<!DOCTYPE html>





<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 3.9.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/favicon.ico?v=7.4.0">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon.ico?v=7.4.0">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon.ico?v=7.4.0">
  <link rel="mask-icon" href="/images/logo.svg?v=7.4.0" color="#222">

<link rel="stylesheet" href="/css/main.css?v=7.4.0">


<link rel="stylesheet" href="/lib/font-awesome/css/font-awesome.min.css?v=4.7.0">


<script id="hexo-configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Gemini',
    version: '7.4.0',
    exturl: false,
    sidebar: {"position":"left","display":"post","offset":12,"onmobile":false,"dimmer":false},
    copycode: {"enable":false,"show_result":false,"style":null},
    back2top: {"enable":true,"sidebar":false,"scrollpercent":false},
    bookmark: {"enable":false,"color":"#222","save":"auto"},
    fancybox: false,
    mediumzoom: false,
    lazyload: false,
    pangu: false,
    algolia: {
      appID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    },
    localsearch: {"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},
    path: 'search.json',
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    translation: {
      copy_button: '复制',
      copy_success: '复制成功',
      copy_failure: '复制失败'
    },
    sidebarPadding: 40
  };
</script>

  <meta name="description" content="0. 基本概念镜像镜像包含操作系统完整的 root 文件系统，其体积往往是庞大的，因此在 Docker 设计时，就充分利用 Union FS 的技术，将其设计为分层存储的架构。所以严格来说，镜像并非是像一个 ISO 那样的打包文件，镜像只是一个虚拟的概念，其实际体现并非由一个文件组成，而是由一组文件系统组成，或者说，由多层文件系统联合组成。 容器每一个容器运行时，是以镜像为基础层，在其上创建一个当">
<meta name="keywords" content="docker">
<meta property="og:type" content="article">
<meta property="og:title" content="docker从入门到实践">
<meta property="og:url" content="https://unix2dos.github.io/p/3fc66628.html">
<meta property="og:site_name" content="Levon&#39;s Blog">
<meta property="og:description" content="0. 基本概念镜像镜像包含操作系统完整的 root 文件系统，其体积往往是庞大的，因此在 Docker 设计时，就充分利用 Union FS 的技术，将其设计为分层存储的架构。所以严格来说，镜像并非是像一个 ISO 那样的打包文件，镜像只是一个虚拟的概念，其实际体现并非由一个文件组成，而是由一组文件系统组成，或者说，由多层文件系统联合组成。 容器每一个容器运行时，是以镜像为基础层，在其上创建一个当">
<meta property="og:locale" content="zh-CN">
<meta property="og:updated_time" content="2019-09-27T15:36:51.133Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="docker从入门到实践">
<meta name="twitter:description" content="0. 基本概念镜像镜像包含操作系统完整的 root 文件系统，其体积往往是庞大的，因此在 Docker 设计时，就充分利用 Union FS 的技术，将其设计为分层存储的架构。所以严格来说，镜像并非是像一个 ISO 那样的打包文件，镜像只是一个虚拟的概念，其实际体现并非由一个文件组成，而是由一组文件系统组成，或者说，由多层文件系统联合组成。 容器每一个容器运行时，是以镜像为基础层，在其上创建一个当">
  <link rel="canonical" href="https://unix2dos.github.io/p/3fc66628">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome: false,
    isPost: true,
    isPage: false,
    isArchive: false
  };
</script>

  <title>docker从入门到实践 | Levon's Blog</title>
  








  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .logo,
  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-CN">
  <div class="container use-motion">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-meta">

    <div>
      <a href="/" class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">Levon's Blog</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
        <h1 class="site-subtitle" itemprop="description">微信:  L6241425</h1>
      
  </div>

  <div class="site-nav-toggle">
    <button aria-label="切换导航栏">
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>


<nav class="site-nav">
  
  <ul id="menu" class="menu">
      
      
      
        
        <li class="menu-item menu-item-home">
      
    

    <a href="/" rel="section"><i class="menu-item-icon fa fa-fw fa-home"></i> <br>首页</a>

  </li>
      
      
      
        
        <li class="menu-item menu-item-archives">
      
    

    <a href="/archives/" rel="section"><i class="menu-item-icon fa fa-fw fa-archive"></i> <br>时光</a>

  </li>
      
      
      
        
        <li class="menu-item menu-item-categories">
      
    

    <a href="/categories/" rel="section"><i class="menu-item-icon fa fa-fw fa-th"></i> <br>分类</a>

  </li>
      
      
      
        
        <li class="menu-item menu-item-tags">
      
    

    <a href="/tags/" rel="section"><i class="menu-item-icon fa fa-fw fa-tags"></i> <br>标签</a>

  </li>
      <li class="menu-item menu-item-search">
        <a href="javascript:;" class="popup-trigger">
        
          <i class="menu-item-icon fa fa-search fa-fw"></i> <br>搜索</a>
      </li>
    
  </ul>

</nav>
  <div class="site-search">
    <div class="popup search-popup">
    <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocorrect="off" autocapitalize="none"
           placeholder="搜索..." spellcheck="false"
           type="text" id="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result"></div>

</div>
<div class="search-pop-overlay"></div>

  </div>
</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
            

          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
      <article itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block post">
    <link itemprop="mainEntityOfPage" href="https://unix2dos.github.io/p/3fc66628.html">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="Levon">
      <meta itemprop="description" content="Never Give Up">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Levon's Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">docker从入门到实践

          
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              
                
              

              <time title="创建时间：2018-05-26 22:01:19" itemprop="dateCreated datePublished" datetime="2018-05-26T22:01:19+08:00">2018-05-26</time>
            </span>
          
            

            
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="fa fa-calendar-check-o"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2019-09-27 23:36:51" itemprop="dateModified" datetime="2019-09-27T23:36:51+08:00">2019-09-27</time>
              </span>
            
          
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing"><a href="/categories/2-linux系统/" itemprop="url" rel="index"><span itemprop="name">2-linux系统</span></a></span>

                
                
                  ，
                
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing"><a href="/categories/2-linux系统/docker/" itemprop="url" rel="index"><span itemprop="name">docker</span></a></span>

                
                
              
            </span>
          

          
  
  <span class="post-meta-item">
    
      <span class="post-meta-item-icon">
        <i class="fa fa-comment-o"></i>
      </span>
        
      
      <span class="post-meta-item-text">Disqus：</span>
    
    <a title="disqus" href="/p/3fc66628.html#comments" itemprop="discussionUrl"><span class="post-comments-count disqus-comment-count" data-disqus-identifier="p/3fc66628.html" itemprop="commentCount"></span></a>
  </span>
  
  
          <br>
            <span class="post-meta-item" title="本文字数">
              <span class="post-meta-item-icon">
                <i class="fa fa-file-word-o"></i>
              </span>
              
                <span class="post-meta-item-text">本文字数：</span>
              
              <span>35k</span>
            </span>
          
            <span class="post-meta-item" title="阅读时长">
              <span class="post-meta-item-icon">
                <i class="fa fa-clock-o"></i>
              </span>
              
                <span class="post-meta-item-text">阅读时长 &asymp;</span>
              
              <span>31 分钟</span>
            </span>
          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <h1 id="0-基本概念"><a href="#0-基本概念" class="headerlink" title="0. 基本概念"></a>0. 基本概念</h1><h4 id="镜像"><a href="#镜像" class="headerlink" title="镜像"></a>镜像</h4><p>镜像包含操作系统完整的 <code>root</code> 文件系统，其体积往往是庞大的，因此在 Docker 设计时，就充分利用 <a href="https://en.wikipedia.org/wiki/Union_mount" target="_blank" rel="noopener">Union FS</a> 的技术，将其设计为分层存储的架构。所以严格来说，镜像并非是像一个 ISO 那样的打包文件，镜像只是一个虚拟的概念，其实际体现并非由一个文件组成，而是由一组文件系统组成，或者说，由多层文件系统联合组成。</p>
<h4 id="容器"><a href="#容器" class="headerlink" title="容器"></a>容器</h4><p>每一个容器运行时，是以镜像为基础层，在其上创建一个当前容器的存储层，我们可以称这个为容器运行时读写而准备的存储层为<strong>容器存储层</strong>。</p>
<p>容器不应该向其存储层内写入任何数据，容器存储层要保持无状态化。所有的文件写入操作，都应该使用 <a href="https://yeasy.gitbooks.io/docker_practice/content/data_management/volume.html" target="_blank" rel="noopener">数据卷（Volume）</a>、或者绑定宿主目录，在这些位置的读写会跳过容器存储层，直接对宿主（或网络存储）发生读写，其性能和稳定性更高。</p>
<p>数据卷的生存周期独立于容器，容器消亡，数据卷不会消亡。因此，使用数据卷后，容器删除或者重新运行之后，数据却不会丢失。</p>
<a id="more"></a>

<h4 id="仓库"><a href="#仓库" class="headerlink" title="仓库"></a>仓库</h4><p>一个仓库会包含同一个软件不同版本的镜像，而标签就常用于对应该软件的各个版本。我们可以通过 <code>&lt;仓库名&gt;:&lt;标签&gt;</code> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签，将以 <code>latest</code> 作为默认标签。</p>
<p>以 <a href="https://store.docker.com/images/ubuntu" target="_blank" rel="noopener">Ubuntu 镜像</a> 为例，<code>ubuntu</code> 是仓库的名字，其内包含有不同的版本标签，如，<code>14.04</code>, <code>16.04</code>。我们可以通过 <code>ubuntu:14.04</code>，或者 <code>ubuntu:16.04</code> 来具体指定所需哪个版本的镜像。如果忽略了标签，比如 <code>ubuntu</code>，那将视为 <code>ubuntu:latest</code>。</p>
<p>仓库名经常以 <em>两段式路径</em> 形式出现，比如 <code>jwilder/nginx-proxy</code>，前者往往意味着 Docker Registry 多用户环境下的用户名，后者则往往是对应的软件名。但这并非绝对，取决于所使用的具体 Docker Registry 的软件或服务。</p>
<h1 id="1-使用镜像"><a href="#1-使用镜像" class="headerlink" title="1. 使用镜像"></a>1. 使用镜像</h1><h4 id="获取镜像"><a href="#获取镜像" class="headerlink" title="获取镜像"></a>获取镜像</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]</span><br></pre></td></tr></table></figure>

<ul>
<li>Docker 镜像仓库地址：地址的格式一般是 <code>&lt;域名/IP&gt;[:端口号]</code>。默认地址是 Docker Hub。</li>
<li>仓库名：如之前所说，这里的仓库名是两段式名称，即 <code>&lt;用户名&gt;/&lt;软件名&gt;</code>。对于 Docker Hub，如果不给出用户名，则默认为 <code>library</code>，也就是官方镜像。</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker pull ubuntu:16.04</span><br><span class="line">16.04: Pulling from library/ubuntu</span><br></pre></td></tr></table></figure>

<h4 id="运行容器"><a href="#运行容器" class="headerlink" title="运行容器"></a>运行容器</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">docker run -it --rm \</span><br><span class="line">    ubuntu:16.04 \</span><br><span class="line">    bash</span><br></pre></td></tr></table></figure>

<ul>
<li><code>-it</code>：这是两个参数，一个是 <code>-i</code>：交互式操作，一个是 <code>-t</code> 终端。我们这里打算进入 <code>bash</code> 执行一些命令并查看返回结果，因此我们需要交互式终端。</li>
<li><code>--rm</code>：这个参数是说容器退出后随之将其删除。默认情况下，为了排障需求，退出的容器并不会立即删除，除非手动 <code>docker rm</code>。我们这里只是随便执行个命令，看看结果，不需要排障和保留结果，因此使用 <code>--rm</code> 可以避免浪费空间。</li>
<li><code>ubuntu:16.04</code>：这是指用 <code>ubuntu:16.04</code> 镜像为基础来启动容器。</li>
<li><code>bash</code>：放在镜像名后的是<strong>命令</strong>，这里我们希望有个交互式 Shell，因此用的是 <code>bash</code></li>
</ul>
<h4 id="列出镜像"><a href="#列出镜像" class="headerlink" title="列出镜像"></a>列出镜像</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker images</span><br></pre></td></tr></table></figure>

<p>列表包含了 <code>仓库名</code>、<code>标签</code>、<code>镜像 ID</code>、<code>创建时间</code> 以及 <code>所占用的空间</code>。</p>
<p><strong>镜像 ID</strong> 则是镜像的唯一标识，一个镜像可以对应多个<strong>标签</strong>。</p>
<h4 id="虚悬镜像"><a href="#虚悬镜像" class="headerlink" title="虚悬镜像"></a>虚悬镜像</h4><p> <code>docker pull</code> 可能导致这种情况，<code>docker build</code> 也同样可以导致这种现象。由于新旧镜像同名，旧镜像名称被取消，从而出现仓库名、标签均为 <code>&lt;none&gt;</code> 的镜像。这类无标签镜像也被称为 <strong>虚悬镜像(dangling image)</strong></p>
<p>一般来说，虚悬镜像已经失去了存在的价值，是可以随意删除的，可以用下面的命令删除。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker image prune</span><br></pre></td></tr></table></figure>

<h4 id="删除本地镜像"><a href="#删除本地镜像" class="headerlink" title="删除本地镜像"></a>删除本地镜像</h4><p>如果要删除本地的镜像，可以使用 <code>docker image rm</code> 命令，其格式为：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker image rm [选项] &lt;镜像1&gt; [&lt;镜像2&gt; ...]</span><br></pre></td></tr></table></figure>

<h4 id="利用-commit-理解镜像构成"><a href="#利用-commit-理解镜像构成" class="headerlink" title="利用 commit 理解镜像构成"></a>利用 commit 理解镜像构成</h4><p>我们修改了容器的文件，也就是改动了容器的存储层。我们可以通过 <code>docker diff</code> 命令看到具体的改动。</p>
<p> 现在我们定制好了变化，我们希望能将其保存下来形成镜像。</p>
<p>要知道，当我们运行一个容器的时候（如果不使用卷的话），我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 <code>docker commit</code> 命令，可以将容器的存储层保存下来成为镜像。换句话说，就是在原有镜像的基础上，再叠加上容器的存储层，并构成新的镜像。以后我们运行这个新镜像的时候，就会拥有原有容器最后的文件变化。</p>
<p><code>docker commit</code> 的语法格式为：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker commit [选项] &lt;容器ID或容器名&gt; [&lt;仓库名&gt;[:&lt;标签&gt;]]</span><br></pre></td></tr></table></figure>

<p>我们可以用下面的命令将容器保存为镜像：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ docker commit \</span><br><span class="line">    --author &quot;Tao Wang &lt;twang2218@gmail.com&gt;&quot; \</span><br><span class="line">    --message &quot;修改了默认网页&quot; \</span><br><span class="line">    webserver \</span><br><span class="line">    nginx:v2</span><br><span class="line">sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214</span><br></pre></td></tr></table></figure>



<h4 id="慎用-docker-commit"><a href="#慎用-docker-commit" class="headerlink" title="慎用 docker commit"></a>慎用 <code>docker commit</code></h4><p>使用 <code>docker commit</code> 意味着所有对镜像的操作都是黑箱操作，生成的镜像也被称为<strong>黑箱镜像</strong>，换句话说，就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像，别人根本无从得知。而且，即使是这个制作镜像的人，过一段时间后也无法记清具体在操作的。虽然 <code>docker diff</code> 或许可以告诉得到一些线索，但是远远不到可以确保生成一致镜像的地步。这种黑箱镜像的维护工作是非常痛苦的。</p>
<p>而且，回顾之前提及的镜像所使用的分层存储的概念，除当前层外，之前的每一层都是不会发生改变的，换句话说，任何修改的结果仅仅是在当前层进行标记、添加、修改，而不会改动上一层。如果使用 <code>docker commit</code> 制作镜像，以及后期修改的话，每一次修改都会让镜像更加臃肿一次，所删除的上一层的东西并不会丢失，会一直如影随形的跟着这个镜像，即使根本无法访问到。这会让镜像更加臃肿。</p>
<h4 id><a href="#" class="headerlink" title></a></h4><h1 id="2-Dockerfile-定制镜像"><a href="#2-Dockerfile-定制镜像" class="headerlink" title="2. Dockerfile 定制镜像"></a>2. Dockerfile 定制镜像</h1><p>Dockerfile 是一个文本文件，其内包含了一条条的<strong>指令(Instruction)</strong>，每一条指令构建一层，因此每一条指令的内容，就是描述该层应当如何构建。</p>
<h3 id="2-A-构建镜像"><a href="#2-A-构建镜像" class="headerlink" title="2.A 构建镜像"></a>2.A 构建镜像</h3><hr>
<p>在 <code>Dockerfile</code> 文件所在目录执行：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker build -t nginx:v3 .</span><br></pre></td></tr></table></figure>

<p>这里我们使用了 <code>docker build</code> 命令进行镜像构建。其格式为：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker build [选项] &lt;上下文路径/URL/-&gt;</span><br></pre></td></tr></table></figure>

<h4 id="2-1-镜像构建上下文（Context）-上下文路径就是-docker-build-指定的"><a href="#2-1-镜像构建上下文（Context）-上下文路径就是-docker-build-指定的" class="headerlink" title="2.1 镜像构建上下文（Context） 上下文路径就是 docker build 指定的"></a>2.1 镜像构建上下文（Context） 上下文路径就是 docker build 指定的</h4><p>如果注意，会看到 <code>docker build</code> 命令最后有一个 <code>.</code>。<code>.</code> 表示当前目录，而 <code>Dockerfile</code> 就在当前目录，因此不少初学者以为这个路径是在指定 <code>Dockerfile</code> 所在路径，这么理解其实是不准确的。如果对应上面的命令格式，你可能会发现，这是在指定<strong>上下文路径</strong>。那么什么是上下文呢？</p>
<p>首先我们要理解 <code>docker build</code> 的工作原理。Docker 在运行时分为 Docker 引擎（也就是服务端守护进程）和客户端工具。Docker 的引擎提供了一组 REST API，被称为 <a href="https://docs.docker.com/engine/reference/api/docker_remote_api/" target="_blank" rel="noopener">Docker Remote API</a>，而如 <code>docker</code> 命令这样的客户端工具，则是通过这组 API 与 Docker 引擎交互，从而完成各种功能。因此，虽然表面上我们好像是在本机执行各种 <code>docker</code> 功能，但实际上，一切都是使用的远程调用形式在服务端（Docker 引擎）完成。也因为这种 C/S 设计，让我们操作远程服务器的 Docker 引擎变得轻而易举。</p>
<p>当我们进行镜像构建的时候，并非所有定制都会通过 <code>RUN</code> 指令完成，经常会需要将一些本地文件复制进镜像，比如通过 <code>COPY</code> 指令、<code>ADD</code> 指令等。而 <code>docker build</code> 命令构建镜像，其实并非在本地构建，而是在服务端，也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中，如何才能让服务端获得本地文件呢？</p>
<p>这就引入了上下文的概念。当构建的时候，用户会指定构建镜像上下文的路径，<code>docker build</code> 命令得知这个路径后，会将路径下的所有内容打包，然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后，展开就会获得构建镜像所需的一切文件。</p>
<p>如果在 <code>Dockerfile</code> 中这么写：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">COPY ./package.json /app/</span><br></pre></td></tr></table></figure>

<p>这并不是要复制执行 <code>docker build</code> 命令所在的目录下的 <code>package.json</code>，也不是复制 <code>Dockerfile</code> 所在目录下的 <code>package.json</code>，而是复制 <strong>上下文（context）</strong> 目录下的 <code>package.json</code>。</p>
<p>因此，<code>COPY</code> 这类指令中的源文件的路径都是<em>相对路径</em>。这也是初学者经常会问的为什么 <code>COPY ../package.json /app</code> 或者 <code>COPY /opt/xxxx /app</code> 无法工作的原因，因为这些路径已经超出了上下文的范围，Docker 引擎无法获得这些位置的文件。如果真的需要那些文件，应该将它们复制到上下文目录中去。</p>
<p>现在就可以理解刚才的命令 <code>docker build -t nginx:v3 .</code> 中的这个 <code>.</code>，实际上是在指定上下文的目录，<code>docker build</code> 命令会将该目录下的内容打包交给 Docker 引擎以帮助构建镜像。</p>
<p> 理解构建上下文对于镜像构建是很重要的，避免犯一些不应该的错误。比如有些初学者在发现 <code>COPY /opt/xxxx /app</code> 不工作后，于是干脆将 <code>Dockerfile</code> 放到了硬盘根目录去构建，结果发现 <code>docker build</code> 执行后，在发送一个几十 GB 的东西，极为缓慢而且很容易构建失败。那是因为这种做法是在让 <code>docker build</code> 打包整个硬盘，这显然是使用错误。</p>
<p>一般来说，应该会将 <code>Dockerfile</code> 置于一个空目录下，或者项目根目录下。如果该目录下没有所需文件，那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎，那么可以用 <code>.gitignore</code> 一样的语法写一个 <code>.dockerignore</code>，该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。 </p>
<p>那么为什么会有人误以为 <code>.</code> 是指定 <code>Dockerfile</code> 所在目录呢？这是因为在默认情况下，如果不额外指定 <code>Dockerfile</code> 的话，会将上下文目录下的名为 <code>Dockerfile</code> 的文件作为 Dockerfile。</p>
<p>这只是默认行为，实际上 <code>Dockerfile</code> 的文件名并不要求必须为 <code>Dockerfile</code>，而且并不要求必须位于上下文目录中，比如可以用 <code>-f ../Dockerfile.php</code> 参数指定某个文件作为 <code>Dockerfile</code>。</p>
<p>当然，一般大家习惯性的会使用默认的文件名 <code>Dockerfile</code>，以及会将其置于镜像构建上下文目录中。</p>
<h4 id="2-2-其它-docker-build-的用法"><a href="#2-2-其它-docker-build-的用法" class="headerlink" title="2.2 其它 docker build 的用法"></a>2.2 其它 <code>docker build</code> 的用法</h4><h5 id="1-直接用-Git-repo-进行构建"><a href="#1-直接用-Git-repo-进行构建" class="headerlink" title="1. 直接用 Git repo 进行构建"></a>1. 直接用 Git repo 进行构建</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker build https://github.com/twang2218/gitlab-ce-zh.git#:8.14</span><br></pre></td></tr></table></figure>

<h5 id="2-用给定的-tar-压缩包构建"><a href="#2-用给定的-tar-压缩包构建" class="headerlink" title="2. 用给定的 tar 压缩包构建"></a>2. 用给定的 tar 压缩包构建</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker build http://server/context.tar.gz</span><br></pre></td></tr></table></figure>

<p>如果所给出的 URL 不是个 Git repo，而是个 <code>tar</code> 压缩包，那么 Docker 引擎会下载这个包，并自动解压缩，以其作为上下文，开始构建。</p>
<h5 id="3-从标准输入中读取-Dockerfile-进行构建"><a href="#3-从标准输入中读取-Dockerfile-进行构建" class="headerlink" title="3. 从标准输入中读取 Dockerfile 进行构建"></a>3. 从标准输入中读取 Dockerfile 进行构建</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker build - &lt; Dockerfile</span><br></pre></td></tr></table></figure>

<p>或</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat Dockerfile | docker build -</span><br></pre></td></tr></table></figure>

<h5 id="4-从标准输入中读取上下文压缩包进行构建"><a href="#4-从标准输入中读取上下文压缩包进行构建" class="headerlink" title="4. 从标准输入中读取上下文压缩包进行构建"></a>4. 从标准输入中读取上下文压缩包进行构建</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker build - &lt; context.tar.gz</span><br></pre></td></tr></table></figure>

<h3 id="2-B-dockerfile-指令"><a href="#2-B-dockerfile-指令" class="headerlink" title="2.B dockerfile 指令"></a>2.B dockerfile 指令</h3><hr>
<h4 id="2-1-FROM-指定基础镜像"><a href="#2-1-FROM-指定基础镜像" class="headerlink" title="2.1 FROM 指定基础镜像"></a>2.1 FROM 指定基础镜像</h4><p>所谓定制镜像，那一定是以一个镜像为基础，在其上进行定制。就像我们之前运行了一个 <code>nginx</code> 镜像的容器，再进行修改一样，基础镜像是必须指定的。而 <code>FROM</code> 就是指定<strong>基础镜像</strong>，因此一个 <code>Dockerfile</code> 中 <code>FROM</code> 是必备的指令，并且必须是第一条指令。</p>
<p>在 <a href="https://store.docker.com/" target="_blank" rel="noopener">Docker Store</a> 上有非常多的高质量的官方镜像，有可以直接拿来使用的服务类的镜像，如 <a href="https://store.docker.com/images/nginx/" target="_blank" rel="noopener"><code>nginx</code></a>、<a href="https://store.docker.com/images/redis/" target="_blank" rel="noopener"><code>redis</code></a>、<a href="https://store.docker.com/images/mongo/" target="_blank" rel="noopener"><code>mongo</code></a>、<a href="https://store.docker.com/images/mysql/" target="_blank" rel="noopener"><code>mysql</code></a>、<a href="https://store.docker.com/images/httpd/" target="_blank" rel="noopener"><code>httpd</code></a>、<a href="https://store.docker.com/images/php/" target="_blank" rel="noopener"><code>php</code></a>、<a href="https://store.docker.com/images/tomcat/" target="_blank" rel="noopener"><code>tomcat</code></a> 等；也有一些方便开发、构建、运行各种语言应用的镜像，如 <a href="https://store.docker.com/images/node" target="_blank" rel="noopener"><code>node</code></a>、<a href="https://store.docker.com/images/openjdk/" target="_blank" rel="noopener"><code>openjdk</code></a>、<a href="https://store.docker.com/images/python/" target="_blank" rel="noopener"><code>python</code></a>、<a href="https://store.docker.com/images/ruby/" target="_blank" rel="noopener"><code>ruby</code></a>、<a href="https://store.docker.com/images/golang/" target="_blank" rel="noopener"><code>golang</code></a> 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。</p>
<p>如果没有找到对应服务的镜像，官方镜像中还提供了一些更为基础的操作系统镜像，如 <a href="https://store.docker.com/images/ubuntu/" target="_blank" rel="noopener"><code>ubuntu</code></a>、<a href="https://store.docker.com/images/debian/" target="_blank" rel="noopener"><code>debian</code></a>、<a href="https://store.docker.com/images/centos/" target="_blank" rel="noopener"><code>centos</code></a>、<a href="https://store.docker.com/images/fedora/" target="_blank" rel="noopener"><code>fedora</code></a>、<a href="https://store.docker.com/images/alpine/" target="_blank" rel="noopener"><code>alpine</code></a> 等，这些操作系统的软件库为我们提供了更广阔的扩展空间。</p>
<p>除了选择现有镜像为基础镜像外，Docker 还存在一个特殊的镜像，名为 <code>scratch</code>。这个镜像是虚拟的概念，并不实际存在，它表示一个空白的镜像。</p>
<h4 id="2-2-RUN-执行命令"><a href="#2-2-RUN-执行命令" class="headerlink" title="2.2 RUN 执行命令"></a>2.2 RUN 执行命令</h4><p><code>RUN</code> 指令是用来执行命令行命令的。由于命令行的强大能力，<code>RUN</code> 指令在定制镜像时是最常用的指令之一。其格式有两种：</p>
<ul>
<li><em>shell</em> 格式：<code>RUN &lt;命令&gt;</code>，就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 <code>RUN</code> 指令就是这种格式。</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">RUN echo &apos;&lt;h1&gt;Hello, Docker!&lt;/h1&gt;&apos; &gt; /usr/share/nginx/html/index.html</span><br></pre></td></tr></table></figure>

<ul>
<li><em>exec</em> 格式：<code>RUN [&quot;可执行文件&quot;, &quot;参数1&quot;, &quot;参数2&quot;]</code>，这更像是函数调用中的格式。</li>
</ul>
<p>之前说过，Dockerfile 中每一个指令都会建立一层，<code>RUN</code> 也不例外。每一个 <code>RUN</code> 的行为，就和刚才我们手工建立镜像的过程一样：新建立一层，在其上执行这些命令，执行结束后，<code>commit</code> 这一层的修改，构成新的镜像。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">FROM debian:jessie</span><br><span class="line"></span><br><span class="line">RUN buildDeps=&apos;gcc libc6-dev make&apos; \</span><br><span class="line">    &amp;&amp; apt-get update \</span><br><span class="line">    &amp;&amp; apt-get install -y $buildDeps \</span><br><span class="line">    &amp;&amp; wget -O redis.tar.gz &quot;http://download.redis.io/releases/redis-3.2.5.tar.gz&quot; \</span><br><span class="line">    &amp;&amp; mkdir -p /usr/src/redis \</span><br><span class="line">    &amp;&amp; tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \</span><br><span class="line">    &amp;&amp; make -C /usr/src/redis \</span><br><span class="line">    &amp;&amp; make -C /usr/src/redis install \</span><br><span class="line">    &amp;&amp; rm -rf /var/lib/apt/lists/* \</span><br><span class="line">    &amp;&amp; rm redis.tar.gz \</span><br><span class="line">    &amp;&amp; rm -r /usr/src/redis \</span><br><span class="line">    &amp;&amp; apt-get purge -y --auto-remove $buildDeps</span><br></pre></td></tr></table></figure>

<p> 首先，之前所有的命令只有一个目的，就是编译、安装 redis 可执行文件。因此没有必要建立很多层，这只是一层的事情。因此，这里没有使用很多个 <code>RUN</code> 对一一对应不同的命令，而是仅仅使用一个 <code>RUN</code> 指令，并使用 <code>&amp;&amp;</code> 将各个所需命令串联起来。将之前的 7 层，简化为了 1 层。在撰写 Dockerfile 的时候，要经常提醒自己，这并不是在写 Shell 脚本，而是在定义每一层该如何构建。</p>
<p>并且，这里为了格式化还进行了换行。Dockerfile 支持 Shell 类的行尾添加 <code>\</code> 的命令换行方式，以及行首 <code>#</code> 进行注释的格式。良好的格式，比如换行、缩进、注释等，会让维护、排障更为容易，这是一个比较好的习惯。</p>
<p>此外，还可以看到这一组命令的最后添加了清理工作的命令，删除了为了编译构建所需要的软件，清理了所有下载、展开的文件，并且还清理了 <code>apt</code> 缓存文件。这是很重要的一步，我们之前说过，镜像是多层存储，每一层的东西并不会在下一层被删除，会一直跟随着镜像。因此镜像构建时，一定要确保每一层只添加真正需要添加的东西，任何无关的东西都应该清理掉。</p>
<h4 id="2-3-COPY-复制文件"><a href="#2-3-COPY-复制文件" class="headerlink" title="2.3 COPY 复制文件"></a>2.3 COPY 复制文件</h4><p>格式：</p>
<ul>
<li><code>COPY &lt;源路径&gt;... &lt;目标路径&gt;</code></li>
<li><code>COPY [&quot;&lt;源路径1&gt;&quot;,... &quot;&lt;目标路径&gt;&quot;]</code></li>
</ul>
<p><code>COPY</code> 指令将从构建上下文目录中 <code>&lt;源路径&gt;</code> 的文件/目录复制到新的一层的镜像内的 <code>&lt;目标路径&gt;</code> 位置。比如：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">COPY package.json /usr/src/app/</span><br></pre></td></tr></table></figure>

<p><code>&lt;源路径&gt;</code> 可以是多个，甚至可以是通配符，其通配符规则要满足 Go 的 <a href="https://golang.org/pkg/path/filepath/#Match" target="_blank" rel="noopener"><code>filepath.Match</code></a> 规则，如：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">COPY hom* /mydir/</span><br><span class="line">COPY hom?.txt /mydir/</span><br></pre></td></tr></table></figure>

<p><code>&lt;目标路径&gt;</code> 可以是容器内的绝对路径，也可以是相对于工作目录的相对路径（工作目录可以用 <code>WORKDIR</code>指令来指定）。目标路径不需要事先创建，如果目录不存在会在复制文件前先行创建缺失目录。</p>
<p>此外，还需要注意一点，使用 <code>COPY</code> 指令，源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。</p>
<h4 id="2-4-ADD-更高级的复制文件"><a href="#2-4-ADD-更高级的复制文件" class="headerlink" title="2.4 ADD 更高级的复制文件"></a>2.4 ADD 更高级的复制文件</h4><p><code>ADD</code> 指令和 <code>COPY</code> 的格式和性质基本一致。但是在 <code>COPY</code> 基础上增加了一些功能。</p>
<p> 比如 <code>&lt;源路径&gt;</code> 可以是一个 <code>URL</code>，这种情况下，Docker 引擎会试图去下载这个链接的文件放到 <code>&lt;目标路径&gt;</code> 去。下载后的文件权限自动设置为 <code>600</code>，如果这并不是想要的权限，那么还需要增加额外的一层 <code>RUN</code>进行权限调整，另外，如果下载的是个压缩包，需要解压缩，也一样还需要额外的一层 <code>RUN</code> 指令进行解压缩。所以不如直接使用 <code>RUN</code> 指令，然后使用 <code>wget</code> 或者 <code>curl</code> 工具下载，处理权限、解压缩、然后清理无用文件更合理。因此，这个功能其实并不实用，而且不推荐使用。</p>
<h4 id="2-5-CMD-容器启动命令"><a href="#2-5-CMD-容器启动命令" class="headerlink" title="2.5 CMD 容器启动命令"></a>2.5 CMD 容器启动命令</h4><p><code>CMD</code> 指令的格式和 <code>RUN</code> 相似，也是两种格式：</p>
<ul>
<li><p><code>shell</code> 格式：<code>CMD &lt;命令&gt;</code></p>
</li>
<li><p><code>exec</code> 格式：<code>CMD [&quot;可执行文件&quot;, &quot;参数1&quot;, &quot;参数2&quot;...]</code></p>
</li>
<li><p>参数列表格式：<code>CMD [&quot;参数1&quot;, &quot;参数2&quot;...]</code>。在指定了 <code>ENTRYPOINT</code> 指令后，用 <code>CMD</code> 指定具体的参数。</p>
</li>
</ul>
<p>之前介绍容器的时候曾经说过，Docker 不是虚拟机，容器就是进程。既然是进程，那么在启动容器的时候，需要指定所运行的程序及参数。<code>CMD</code> 指令就是用于指定默认的容器主进程的启动命令的。</p>
<p>在运行时可以指定新的命令来替代镜像设置中的这个默认命令，比如，<code>ubuntu</code> 镜像默认的 <code>CMD</code> 是 <code>/bin/bash</code>，如果我们直接 <code>docker run -it ubuntu</code> 的话，会直接进入 <code>bash</code>。我们也可以在运行时指定运行别的命令，如 <code>docker run -it ubuntu cat /etc/os-release</code>。这就是用 <code>cat /etc/os-release</code> 命令替换了默认的 <code>/bin/bash</code> 命令了，输出了系统版本信息。</p>
<p>在指令格式上，一般推荐使用 <code>exec</code> 格式，这类格式在解析时会被解析为 JSON 数组，因此一定要使用双引号 <code>&quot;</code>，而不要使用单引号。</p>
<p>如果使用 <code>shell</code> 格式的话，实际的命令会被包装为 <code>sh -c</code> 的参数的形式进行执行。比如：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CMD echo $HOME</span><br></pre></td></tr></table></figure>

<p>在实际执行中，会将其变更为：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CMD [ &quot;sh&quot;, &quot;-c&quot;, &quot;echo $HOME&quot; ]</span><br></pre></td></tr></table></figure>

<p>这就是为什么我们可以使用环境变量的原因，因为这些环境变量会被 shell 进行解析处理。</p>
<p>提到 <code>CMD</code> 就不得不提容器中应用在前台执行和后台执行的问题。这是初学者常出现的一个混淆。</p>
<p>Docker 不是虚拟机，容器中的应用都应该以前台执行，而不是像虚拟机、物理机里面那样，用 upstart/systemd 去启动后台服务，容器内没有后台服务的概念。</p>
<p>一些初学者将 <code>CMD</code> 写为：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CMD service nginx start</span><br></pre></td></tr></table></figure>

<p>然后发现容器执行后就立即退出了。甚至在容器内去使用 <code>systemctl</code> 命令结果却发现根本执行不了。这就是因为没有搞明白前台、后台的概念，没有区分容器和虚拟机的差异，依旧在以传统虚拟机的角度去理解容器。   </p>
<p>对于容器而言，其启动程序就是容器应用进程，容器就是为了主进程而存在的，主进程退出，容器就失去了存在的意义，从而退出，其它辅助进程不是它需要关心的东西。</p>
<p>而使用 <code>service nginx start</code> 命令，则是希望 upstart 来以后台守护进程形式启动 <code>nginx</code> 服务。而刚才说了 <code>CMD service nginx start</code> 会被理解为 <code>CMD [ &quot;sh&quot;, &quot;-c&quot;, &quot;service nginx start&quot;]</code>，因此主进程实际上是 <code>sh</code>。那么当 <code>service nginx start</code> 命令结束后，<code>sh</code> 也就结束了，<code>sh</code> 作为主进程退出了，自然就会令容器退出。</p>
<p>正确的做法是直接执行 <code>nginx</code> 可执行文件，并且要求以前台形式运行。比如：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CMD [&quot;nginx&quot;, &quot;-g&quot;, &quot;daemon off;&quot;]</span><br></pre></td></tr></table></figure>

<h4 id="2-6-ENTRYPOINT-入口点"><a href="#2-6-ENTRYPOINT-入口点" class="headerlink" title="2.6 ENTRYPOINT 入口点"></a>2.6 ENTRYPOINT 入口点</h4><p><code>ENTRYPOINT</code> 的目的和 <code>CMD</code> 一样，都是在指定容器启动程序及参数。<code>ENTRYPOINT</code> 在运行时也可以替代，不过比 <code>CMD</code> 要略显繁琐，需要通过 <code>docker run</code> 的参数 <code>--entrypoint</code> 来指定。</p>
<p>当指定了 <code>ENTRYPOINT</code> 后，<code>CMD</code> 的含义就发生了改变，不再是直接的运行其命令，而是将 <code>CMD</code> 的内容作为参数传给 <code>ENTRYPOINT</code> 指令，换句话说实际执行时，将变为：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;ENTRYPOINT&gt; &quot;&lt;CMD&gt;&quot;</span><br></pre></td></tr></table></figure>

<p>那么有了 <code>CMD</code> 后，为什么还要有 <code>ENTRYPOINT</code> 呢？这种 <code>&lt;ENTRYPOINT&gt; &quot;&lt;CMD&gt;&quot;</code> 有什么好处么？让我们来看几个场景。</p>
<h5 id="场景一：让镜像变成像命令一样使用"><a href="#场景一：让镜像变成像命令一样使用" class="headerlink" title="场景一：让镜像变成像命令一样使用"></a>场景一：让镜像变成像命令一样使用</h5><p>假设我们需要一个得知自己当前公网 IP 的镜像，那么可以先用 <code>CMD</code> 来实现：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">FROM ubuntu:16.04</span><br><span class="line">RUN apt-get update \</span><br><span class="line">    &amp;&amp; apt-get install -y curl \</span><br><span class="line">    &amp;&amp; rm -rf /var/lib/apt/lists/*</span><br><span class="line">CMD [ &quot;curl&quot;, &quot;-s&quot;, &quot;http://ip.cn&quot; ]</span><br></pre></td></tr></table></figure>

<p>假如我们使用 <code>docker build -t myip .</code> 来构建镜像的话，如果我们需要查询当前公网 IP，只需要执行：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ docker run myip</span><br><span class="line">当前 IP：61.148.226.66 来自：北京市 联通</span><br></pre></td></tr></table></figure>

<p>嗯，这么看起来好像可以直接把镜像当做命令使用了，不过命令总有参数，如果我们希望加参数呢？比如从上面的 <code>CMD</code> 中可以看到实质的命令是 <code>curl</code>，那么如果我们希望显示 HTTP 头信息，就需要加上 <code>-i</code> 参数。那么我们可以直接加 <code>-i</code> 参数给 <code>docker run myip</code> 么？</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ docker run myip -i</span><br><span class="line">docker: Error response from daemon: invalid header field value &quot;oci runtime error: container_linux.go:247: starting container process caused \&quot;exec: \\\&quot;-i\\\&quot;: executable file not found in $PATH\&quot;\n&quot;.</span><br></pre></td></tr></table></figure>

<p>我们可以看到可执行文件找不到的报错，<code>executable file not found</code>。之前我们说过，跟在镜像名后面的是 <code>command</code>，运行时会替换 <code>CMD</code> 的默认值。因此这里的 <code>-i</code> 替换了原来的 <code>CMD</code>，而不是添加在原来的 <code>curl -s http://ip.cn</code> 后面。而 <code>-i</code> 根本不是命令，所以自然找不到。</p>
<p>现在我们重新用 <code>ENTRYPOINT</code> 来实现这个镜像：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">FROM ubuntu:16.04</span><br><span class="line">RUN apt-get update \</span><br><span class="line">    &amp;&amp; apt-get install -y curl \</span><br><span class="line">    &amp;&amp; rm -rf /var/lib/apt/lists/*</span><br><span class="line">ENTRYPOINT [ &quot;curl&quot;, &quot;-s&quot;, &quot;http://ip.cn&quot; ]</span><br></pre></td></tr></table></figure>

<p>这次我们再来尝试直接使用 <code>docker run myip -i</code>：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">$ docker run myip</span><br><span class="line">当前 IP：61.148.226.66 来自：北京市 联通</span><br><span class="line"></span><br><span class="line">$ docker run myip -i</span><br><span class="line">HTTP/1.1 200 OK</span><br><span class="line">Server: nginx/1.8.0</span><br><span class="line">Date: Tue, 22 Nov 2016 05:12:40 GMT</span><br><span class="line">Content-Type: text/html; charset=UTF-8</span><br><span class="line">Vary: Accept-Encoding</span><br><span class="line">X-Powered-By: PHP/5.6.24-1~dotdeb+7.1</span><br><span class="line">X-Cache: MISS from cache-2</span><br><span class="line">X-Cache-Lookup: MISS from cache-2:80</span><br><span class="line">X-Cache: MISS from proxy-2_6</span><br><span class="line">Transfer-Encoding: chunked</span><br><span class="line">Via: 1.1 cache-2:80, 1.1 proxy-2_6:8006</span><br><span class="line">Connection: keep-alive</span><br><span class="line"></span><br><span class="line">当前 IP：61.148.226.66 来自：北京市 联通</span><br></pre></td></tr></table></figure>

<p>可以看到，这次成功了。这是因为当存在 <code>ENTRYPOINT</code> 后，<code>CMD</code> 的内容将会作为参数传给 <code>ENTRYPOINT</code>，而这里 <code>-i</code> 就是新的 <code>CMD</code>，因此会作为参数传给 <code>curl</code>，从而达到了我们预期的效果。 </p>
<h5 id="场景二：应用运行前的准备工作"><a href="#场景二：应用运行前的准备工作" class="headerlink" title="场景二：应用运行前的准备工作"></a>场景二：应用运行前的准备工作</h5><p> 启动容器就是启动主进程，但有些时候，启动主进程前，需要一些准备工作。</p>
<p>比如 <code>mysql</code> 类的数据库，可能需要一些数据库配置、初始化的工作，这些工作要在最终的 mysql 服务器运行之前解决。</p>
<p>此外，可能希望避免使用 <code>root</code> 用户去启动服务，从而提高安全性，而在启动服务前还需要以 <code>root</code> 身份执行一些必要的准备工作，最后切换到服务用户身份启动服务。或者除了服务外，其它命令依旧可以使用 <code>root</code> 身份执行，方便调试等。</p>
<p>这些准备工作是和容器 <code>CMD</code> 无关的，无论 <code>CMD</code> 为什么，都需要事先进行一个预处理的工作。这种情况下，可以写一个脚本，然后放入 <code>ENTRYPOINT</code> 中去执行，而这个脚本会将接到的参数（也就是 <code>&lt;CMD&gt;</code>）作为命令，在脚本最后执行。比如官方镜像 <code>redis</code> 中就是这么做的：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">FROM alpine:3.4</span><br><span class="line">...</span><br><span class="line">RUN addgroup -S redis &amp;&amp; adduser -S -G redis redis</span><br><span class="line">...</span><br><span class="line">ENTRYPOINT [&quot;docker-entrypoint.sh&quot;]</span><br><span class="line"></span><br><span class="line">EXPOSE 6379</span><br><span class="line">CMD [ &quot;redis-server&quot; ]</span><br></pre></td></tr></table></figure>

<h4 id="2-7-ENV-设置环境变量"><a href="#2-7-ENV-设置环境变量" class="headerlink" title="2.7 ENV 设置环境变量"></a>2.7 ENV 设置环境变量</h4><p>格式有两种：</p>
<ul>
<li><p><code>ENV &lt;key&gt; &lt;value&gt;</code></p>
</li>
<li><p><code>ENV &lt;key1&gt;=&lt;value1&gt; &lt;key2&gt;=&lt;value2&gt;...</code></p>
</li>
</ul>
<p>这个指令很简单，就是设置环境变量而已，无论是后面的其它指令，如 <code>RUN</code>，还是运行时的应用，都可以直接使用这里定义的环境变量。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">ENV NODE_VERSION 7.2.0</span><br><span class="line"></span><br><span class="line">RUN curl -SLO &quot;https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz&quot; \</span><br><span class="line">  &amp;&amp; curl -SLO &quot;https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc&quot; \</span><br></pre></td></tr></table></figure>

<p>下列指令可以支持环境变量展开： <code>ADD</code>、<code>COPY</code>、<code>ENV</code>、<code>EXPOSE</code>、<code>LABEL</code>、<code>USER</code>、<code>WORKDIR</code>、<code>VOLUME</code>、<code>STOPSIGNAL</code>、<code>ONBUILD</code>。</p>
<h4 id="2-8-ARG-构建参数-构建环境的环境变量"><a href="#2-8-ARG-构建参数-构建环境的环境变量" class="headerlink" title="2.8 ARG 构建参数(构建环境的环境变量)"></a>2.8 ARG 构建参数(构建环境的环境变量)</h4><p>格式：<code>ARG &lt;参数名&gt;[=&lt;默认值&gt;]</code></p>
<p>构建参数和 <code>ENV</code> 的效果一样，都是设置环境变量。所不同的是，<code>ARG</code> 所设置的构建环境的环境变量，在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 <code>ARG</code> 保存密码之类的信息，因为 <code>docker history</code> 还是可以看到所有值的。</p>
<p><code>Dockerfile</code> 中的 <code>ARG</code> 指令是定义参数名称，以及定义其默认值。该默认值可以在构建命令 <code>docker build</code> 中用 <code>--build-arg &lt;参数名&gt;=&lt;值&gt;</code> 来覆盖。</p>
<h4 id="2-9-VOLUME-定义匿名卷"><a href="#2-9-VOLUME-定义匿名卷" class="headerlink" title="2.9 VOLUME 定义匿名卷"></a>2.9 VOLUME 定义匿名卷</h4><p>格式为：</p>
<ul>
<li><code>VOLUME [&quot;&lt;路径1&gt;&quot;, &quot;&lt;路径2&gt;&quot;...]</code></li>
<li><code>VOLUME &lt;路径&gt;</code></li>
</ul>
<p>为了防止运行时用户忘记将动态文件所保存目录挂载为卷，在 <code>Dockerfile</code> 中，我们可以事先指定某些目录挂载为匿名卷，这样在运行时如果用户不指定挂载，其应用也可以正常运行，不会向容器存储层写入大量数据。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">VOLUME /data</span><br></pre></td></tr></table></figure>

<p>这里的 <code>/data</code> 目录就会在运行时自动挂载为匿名卷，任何向 <code>/data</code> 中写入的信息都不会记录进容器存储层，从而保证了容器存储层的无状态化。当然，运行时可以覆盖这个挂载设置。比如：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -d -v mydata:/data xxxx</span><br></pre></td></tr></table></figure>

<p> 在这行命令中，就使用了 <code>mydata</code> 这个命名卷挂载到了 <code>/data</code> 这个位置，替代了 <code>Dockerfile</code> 中定义的匿名卷的挂载配置。 </p>
<h4 id="2-10-EXPOSE-声明端口"><a href="#2-10-EXPOSE-声明端口" class="headerlink" title="2.10 EXPOSE 声明端口"></a>2.10 EXPOSE 声明端口</h4><p>格式为 <code>EXPOSE &lt;端口1&gt; [&lt;端口2&gt;...]</code>。</p>
<p><code>EXPOSE</code> 指令是声明运行时容器提供服务端口，这只是一个声明，在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处，一个是帮助镜像使用者理解这个镜像服务的守护端口，以方便配置映射；另一个用处则是在运行时使用随机端口映射时，也就是 <code>docker run -P</code>时，会自动随机映射 <code>EXPOSE</code> 的端口。</p>
<p>要将 <code>EXPOSE</code> 和在运行时使用 <code>-p &lt;宿主端口&gt;:&lt;容器端口&gt;</code> 区分开来。<code>-p</code>，是映射宿主端口和容器端口，换句话说，就是将容器的对应端口服务公开给外界访问，而 <code>EXPOSE</code> 仅仅是声明容器打算使用什么端口而已，并不会自动在宿主进行端口映射。</p>
<h4 id="2-11-WORKDIR-指定工作目录"><a href="#2-11-WORKDIR-指定工作目录" class="headerlink" title="2.11 WORKDIR 指定工作目录"></a>2.11 WORKDIR 指定工作目录</h4><p>格式为 <code>WORKDIR &lt;工作目录路径&gt;</code>。</p>
<p>使用 <code>WORKDIR</code> 指令可以来指定工作目录（或者称为当前目录），以后各层的当前目录就被改为指定的目录，如该目录不存在，<code>WORKDIR</code> 会帮你建立目录。</p>
<p>之前提到一些初学者常犯的错误是把 <code>Dockerfile</code> 等同于 Shell 脚本来书写，这种错误的理解还可能会导致出现下面这样的错误：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">RUN cd /app</span><br><span class="line">RUN echo &quot;hello&quot; &gt; world.txt</span><br></pre></td></tr></table></figure>

<p>如果将这个 <code>Dockerfile</code> 进行构建镜像运行后，会发现找不到 <code>/app/world.txt</code> 文件，或者其内容不是 <code>hello</code>。原因其实很简单，在 Shell 中，连续两行是同一个进程执行环境，因此前一个命令修改的内存状态，会直接影响后一个命令；而在 <code>Dockerfile</code> 中， <strong>这两行 <code>RUN</code> 命令的执行环境根本不同，是两个完全不同的容器。</strong>这就是对 <code>Dockerfile</code> 构建分层存储的概念不了解所导致的错误。</p>
<p>之前说过每一个 <code>RUN</code> 都是启动一个容器、执行命令、然后提交存储层文件变更。第一层 <code>RUN cd /app</code> 的执行仅仅是当前进程的工作目录变更，一个内存上的变化而已，其结果不会造成任何文件变更。而到第二层的时候，启动的是一个全新的容器，跟第一层的容器更完全没关系，自然不可能继承前一层构建过程中的内存变化。</p>
<p>因此如果需要改变以后各层的工作目录的位置，那么应该使用 <code>WORKDIR</code> 指令。</p>
<h4 id="2-12-USER-指定当前用户"><a href="#2-12-USER-指定当前用户" class="headerlink" title="2.12 USER 指定当前用户"></a>2.12 USER 指定当前用户</h4><p>格式：<code>USER &lt;用户名&gt;</code></p>
<p><code>USER</code> 指令和 <code>WORKDIR</code> 相似，都是改变环境状态并影响以后的层。<code>WORKDIR</code> 是改变工作目录，<code>USER</code>则是改变之后层的执行 <code>RUN</code>, <code>CMD</code> 以及 <code>ENTRYPOINT</code> 这类命令的身份。</p>
<p>当然，和 <code>WORKDIR</code> 一样，<code>USER</code> 只是帮助你切换到指定用户而已，这个用户必须是事先建立好的，否则无法切换。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">RUN groupadd -r redis &amp;&amp; useradd -r -g redis redis</span><br><span class="line">USER redis</span><br><span class="line">RUN [ &quot;redis-server&quot; ]</span><br></pre></td></tr></table></figure>



<p>如果以 <code>root</code> 执行的脚本，在执行期间希望改变身份，比如希望以某个已经建立好的用户来运行某个服务进程，不要使用 <code>su</code> 或者 <code>sudo</code>，这些都需要比较麻烦的配置，而且在 TTY 缺失的环境下经常出错。建议使用 <a href="https://github.com/tianon/gosu" target="_blank" rel="noopener"><code>gosu</code></a>。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># 建立 redis 用户，并使用 gosu 换另一个用户执行命令</span><br><span class="line">RUN groupadd -r redis &amp;&amp; useradd -r -g redis redis</span><br><span class="line"># 下载 gosu</span><br><span class="line">RUN wget -O /usr/local/bin/gosu &quot;https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64&quot; \</span><br><span class="line">    &amp;&amp; chmod +x /usr/local/bin/gosu \</span><br><span class="line">    &amp;&amp; gosu nobody true</span><br><span class="line"># 设置 CMD，并以另外的用户执行</span><br><span class="line">CMD [ &quot;exec&quot;, &quot;gosu&quot;, &quot;redis&quot;, &quot;redis-server&quot; ]</span><br></pre></td></tr></table></figure>

<h4 id="2-13-HEALTHCHECK-健康检查"><a href="#2-13-HEALTHCHECK-健康检查" class="headerlink" title="2.13 HEALTHCHECK 健康检查"></a>2.13 HEALTHCHECK 健康检查</h4><p>格式：</p>
<ul>
<li><code>HEALTHCHECK [选项] CMD &lt;命令&gt;</code>：设置检查容器健康状况的命令</li>
<li><code>HEALTHCHECK NONE</code>：如果基础镜像有健康检查指令，使用这行可以屏蔽掉其健康检查指令</li>
</ul>
<p>而自 1.12 之后，Docker 提供了 <code>HEALTHCHECK</code> 指令，通过该指令指定一行命令，用这行命令来判断容器主进程的服务状态是否还正常，从而比较真实的反应容器实际状态。</p>
<p> 当在一个镜像指定了 <code>HEALTHCHECK</code> 指令后，用其启动容器，初始状态会为 <code>starting</code>，在 <code>HEALTHCHECK</code> 指令检查成功后变为 <code>healthy</code>，如果连续一定次数失败，则会变为 <code>unhealthy</code>。</p>
<p><code>HEALTHCHECK</code> 支持下列选项：</p>
<ul>
<li><code>--interval=&lt;间隔&gt;</code>：两次健康检查的间隔，默认为 30 秒；</li>
<li><code>--timeout=&lt;时长&gt;</code>：健康检查命令运行超时时间，如果超过这个时间，本次健康检查就被视为失败，默认 30 秒；</li>
<li><code>--retries=&lt;次数&gt;</code>：当连续失败指定次数后，则将容器状态视为 <code>unhealthy</code>，默认 3 次。</li>
</ul>
<p><strong>和 <code>CMD</code>, <code>ENTRYPOINT</code> 一样，<code>HEALTHCHECK</code> 只可以出现一次，如果写了多个，只有最后一个生效。</strong></p>
<p>假设我们有个镜像是个最简单的 Web 服务，我们希望增加健康检查来判断其 Web 服务是否在正常工作，我们可以用 <code>curl</code> 来帮助判断，其 <code>Dockerfile</code> 的 <code>HEALTHCHECK</code> 可以这么写：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">FROM nginx</span><br><span class="line">RUN apt-get update &amp;&amp; apt-get install -y curl &amp;&amp; rm -rf /var/lib/apt/lists/*</span><br><span class="line">HEALTHCHECK --interval=5s --timeout=3s \</span><br><span class="line">  CMD curl -fs http://localhost/ || exit 1</span><br></pre></td></tr></table></figure>



<h4 id="2-14-ONBUILD-为他人做嫁衣裳"><a href="#2-14-ONBUILD-为他人做嫁衣裳" class="headerlink" title="2.14 ONBUILD 为他人做嫁衣裳"></a>2.14 ONBUILD 为他人做嫁衣裳</h4><p>格式：<code>ONBUILD &lt;其它指令&gt;</code>。</p>
<p><code>ONBUILD</code> 是一个特殊的指令，它后面跟的是其它指令，比如 <code>RUN</code>, <code>COPY</code> 等，而这些指令，在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像，去构建下一级镜像的时候才会被执行。</p>
<p><code>Dockerfile</code> 中的其它指令都是为了定制当前镜像而准备的，唯有 <code>ONBUILD</code> 是为了帮助别人定制自己而准备的。</p>
<h3 id="2-C-docker多阶段构建-多个-FROM-as"><a href="#2-C-docker多阶段构建-多个-FROM-as" class="headerlink" title="2.C docker多阶段构建 (多个 FROM as)"></a>2.C docker多阶段构建 (多个 FROM as)</h3><hr>
<p>我们构建 Docker 镜像时，一种方式是将所有的构建过程编包含在一个 <code>Dockerfile</code> 中，包括项目及其依赖库的编译、测试、打包等流程，这里可能会带来的一些问题：</p>
<ul>
<li><p><code>Dockerfile</code> 特别长，可维护性降低</p>
</li>
<li><p>镜像层次多，镜像体积较大，部署时间变长</p>
</li>
<li><p>源代码存在泄露的风险</p>
</li>
</ul>
<p>Docker v17.05 开始支持多阶段构建 (<code>multistage builds</code>)。</p>
<p>编写 <code>Dockerfile</code> 文件</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">FROM golang:1.9-alpine as builder</span><br><span class="line"></span><br><span class="line">RUN apk --no-cache add git</span><br><span class="line"></span><br><span class="line">WORKDIR /go/src/github.com/go/helloworld/</span><br><span class="line"></span><br><span class="line">RUN go get -d -v github.com/go-sql-driver/mysql</span><br><span class="line"></span><br><span class="line">COPY app.go .</span><br><span class="line"></span><br><span class="line">RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .</span><br><span class="line"></span><br><span class="line">FROM alpine:latest as prod</span><br><span class="line"></span><br><span class="line">RUN apk --no-cache add ca-certificates</span><br><span class="line"></span><br><span class="line">WORKDIR /root/</span><br><span class="line"></span><br><span class="line">COPY --from=0 /go/src/github.com/go/helloworld/app .</span><br><span class="line"></span><br><span class="line">CMD [&quot;./app&quot;]</span><br></pre></td></tr></table></figure>

<p>构建镜像</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker build -t go/helloworld:3 .</span><br></pre></td></tr></table></figure>

<h4 id="只构建某一阶段的镜像"><a href="#只构建某一阶段的镜像" class="headerlink" title="只构建某一阶段的镜像"></a>只构建某一阶段的镜像</h4><p>我们可以使用 <code>as</code> 来为某一阶段命名，例如</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">FROM golang:1.9-alpine as builder</span><br></pre></td></tr></table></figure>

<p>例如当我们只想构建 <code>builder</code> 阶段的镜像时，我们可以在使用 <code>docker build</code> 命令时加上 <code>--target</code>参数即可</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker build --target builder -t username/imagename:tag .</span><br></pre></td></tr></table></figure>

<h4 id="构建时从其他镜像复制文件"><a href="#构建时从其他镜像复制文件" class="headerlink" title="构建时从其他镜像复制文件"></a>构建时从其他镜像复制文件</h4><p>上面例子中我们使用 <code>COPY --from=0 /go/src/github.com/go/helloworld/app .</code> 从上一阶段的镜像中复制文件，我们也可以复制任意镜像中的文件。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf</span><br></pre></td></tr></table></figure>

<h3 id="2-D-其它制作镜像的方式"><a href="#2-D-其它制作镜像的方式" class="headerlink" title="2.D 其它制作镜像的方式"></a>2.D 其它制作镜像的方式</h3><hr>
<h4 id="2-1-从-rootfs-压缩包导入"><a href="#2-1-从-rootfs-压缩包导入" class="headerlink" title="2.1 从 rootfs 压缩包导入"></a>2.1 从 rootfs 压缩包导入</h4><p>格式：<code>docker import [选项] &lt;文件&gt;|&lt;URL&gt;|- [&lt;仓库名&gt;[:&lt;标签&gt;]]</code></p>
<p>压缩包可以是本地文件、远程 Web 文件，甚至是从标准输入中得到。压缩包将会在镜像 <code>/</code> 目录展开，并直接作为镜像第一层提交。</p>
<p>比如我们想要创建一个 <a href="https://openvz.org/Main_Page" target="_blank" rel="noopener">OpenVZ</a> 的 Ubuntu 14.04 <a href="https://openvz.org/Download/template/precreated" target="_blank" rel="noopener">模板</a>的镜像：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ docker import \</span><br><span class="line">    http://download.openvz.org/template/precreated/ubuntu-14.04-x86_64-minimal.tar.gz \</span><br><span class="line">    openvz/ubuntu:14.04</span><br><span class="line">Downloading from http://download.openvz.org/template/precreated/ubuntu-14.04-x86_64-minimal.tar.gz</span><br><span class="line">sha256:f477a6e18e989839d25223f301ef738b69621c4877600ae6467c4e5289822a79B/78.42 MB</span><br></pre></td></tr></table></figure>

<p>这条命令自动下载了 <code>ubuntu-14.04-x86_64-minimal.tar.gz</code> 文件，并且作为根文件系统展开导入，并保存为镜像 <code>openvz/ubuntu:14.04</code>。</p>
<p>导入成功后，我们可以用 <code>docker image ls</code> 看到这个导入的镜像：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ docker image ls openvz/ubuntu</span><br><span class="line">REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE</span><br><span class="line">openvz/ubuntu       14.04               f477a6e18e98        55 seconds ago      214.9 MB</span><br></pre></td></tr></table></figure>

<h4 id="2-2-docker-save-和-docker-load"><a href="#2-2-docker-save-和-docker-load" class="headerlink" title="2.2 docker save 和 docker load"></a>2.2 <code>docker save</code> 和 <code>docker load</code></h4><p>Docker 还提供了 <code>docker load</code> 和 <code>docker save</code> 命令，用以将镜像保存为一个 <code>tar</code> 文件，然后传输到另一个位置上，再加载进来。这是在没有 Docker Registry 时的做法，现在已经不推荐，镜像迁移应该直接使用 Docker Registry，无论是直接使用 Docker Hub 还是使用内网私有 Registry 都可以。</p>
<h5 id="保存镜像"><a href="#保存镜像" class="headerlink" title="保存镜像"></a>保存镜像</h5><p>使用 <code>docker save</code> 命令可以将镜像保存为归档文件。</p>
<p>比如我们希望保存这个 <code>alpine</code> 镜像。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ docker image ls alpine</span><br><span class="line">REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE</span><br><span class="line">alpine              latest              baa5d63471ea        5 weeks ago         4.803 MB</span><br></pre></td></tr></table></figure>

<p>保存镜像的命令为：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker save alpine | gzip &gt; alpine-latest.tar.gz</span><br></pre></td></tr></table></figure>

<p>然后我们将 <code>alpine-latest.tar.gz</code> 文件复制到了到了另一个机器上，可以用下面这个命令加载镜像：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ docker load -i alpine-latest.tar.gz</span><br><span class="line">Loaded image: alpine:latest</span><br></pre></td></tr></table></figure>



<p>如果我们结合这两个命令以及 <code>ssh</code> 甚至 <code>pv</code> 的话，利用 Linux 强大的管道，我们可以写一个命令完成从一个机器将镜像迁移到另一个机器，并且带进度条的功能：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker save &lt;镜像名&gt; | bzip2 | pv | ssh &lt;用户名&gt;@&lt;主机名&gt; &apos;cat | docker load&apos;</span><br></pre></td></tr></table></figure>

<h4 id="2-3-docker-export-save-区别"><a href="#2-3-docker-export-save-区别" class="headerlink" title="2.3 docker export save 区别"></a>2.3 docker export save 区别</h4><ul>
<li>docker save是将一个镜像导出成一个tarball文件，对应的导入命令是docker load，将该文件导入成一个镜像。 </li>
<li>docker export是将一个容器导出成一个tarball文件，对应的导入命令时docker import，将该文件导入成一个镜像（注意不是容器）。  容器快照将会丢弃所有的历史记录和元数据信息</li>
</ul>
<h1 id="3-操作容器"><a href="#3-操作容器" class="headerlink" title="3. 操作容器"></a>3. 操作容器</h1><h4 id="新建并启动"><a href="#新建并启动" class="headerlink" title="新建并启动"></a>新建并启动</h4><p>所需要的命令主要为 <code>docker run</code>。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -t -i ubuntu:14.04 /bin/bash</span><br><span class="line">root@af8bae53bdd3:/#</span><br></pre></td></tr></table></figure>

<p>其中，<code>-t</code> 选项让Docker分配一个伪终端（pseudo-tty）并绑定到容器的标准输入上， <code>-i</code> 则让容器的标准输入保持打开。</p>
<p>当利用 <code>docker run</code> 来创建容器时，Docker 在后台运行的标准操作包括：</p>
<ul>
<li>检查本地是否存在指定的镜像，不存在就从公有仓库下载</li>
<li>利用镜像创建并启动一个容器</li>
<li>分配一个文件系统，并在只读的镜像层外面挂载一层可读写层</li>
<li>从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去</li>
<li>从地址池配置一个 ip 地址给容器</li>
<li>执行用户指定的应用程序</li>
<li>执行完毕后容器被终止</li>
</ul>
<h4 id="启动已终止容器"><a href="#启动已终止容器" class="headerlink" title="启动已终止容器"></a>启动已终止容器</h4><p>可以利用 <code>docker container start</code> 命令，直接将一个已经终止的容器启动运行。</p>
<h4 id="后台运行"><a href="#后台运行" class="headerlink" title="后台运行"></a>后台运行</h4><p>更多的时候，需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时，可以通过添加 <code>-d</code> 参数来实现。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"> docker run -d ubuntu:17.10 /bin/sh -c &quot;while true; do echo hello world; sleep 1; done&quot;</span><br><span class="line">77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a</span><br></pre></td></tr></table></figure>

<p>此时容器会在后台运行并不会把输出的结果 (STDOUT) 打印到宿主机上面(输出结果可以用 <code>docker logs</code>查看)。</p>
<p>要获取容器的输出信息，可以通过 <code>docker container logs</code> 命令。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">docker container logs [container ID or NAMES]</span><br><span class="line">hello world</span><br><span class="line">hello world</span><br><span class="line">hello world</span><br><span class="line">. . .</span><br></pre></td></tr></table></figure>

<h4 id="终止容器"><a href="#终止容器" class="headerlink" title="终止容器"></a>终止容器</h4><p>可以使用 <code>docker container stop</code> 来终止一个运行中的容器。</p>
<h4 id="进入容器"><a href="#进入容器" class="headerlink" title="进入容器"></a>进入容器</h4><p><code>docker exec</code> 后边可以跟多个参数，这里主要说明 <code>-i</code> <code>-t</code> 参数。</p>
<p>只用 <code>-i</code> 参数时，由于没有分配伪终端，界面没有我们熟悉的 Linux 命令提示符，但命令执行结果仍然可以返回。</p>
<p>当 <code>-i</code> <code>-t</code> 参数一起使用时，则可以看到我们熟悉的 Linux 命令提示符。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker exec -it 69d1 bash</span><br><span class="line">root@69d137adef7a:/#</span><br></pre></td></tr></table></figure>

<h4 id="导出容器"><a href="#导出容器" class="headerlink" title="导出容器"></a>导出容器</h4><p>如果要导出本地某个容器，可以使用 <code>docker export</code> 命令。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker export 7691a814370e &gt; ubuntu.tar</span><br></pre></td></tr></table></figure>

<p>这样将导出容器快照到本地文件。</p>
<h4 id="导入容器快照"><a href="#导入容器快照" class="headerlink" title="导入容器快照"></a>导入容器快照</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ cat ubuntu.tar | docker import - test/ubuntu:v1.0</span><br><span class="line">$ docker image ls</span><br><span class="line">REPOSITORY          TAG                 IMAGE ID            CREATED              VIRTUAL SIZE</span><br><span class="line">test/ubuntu         v1.0                9d37a6082e97        About a minute ago   171.3 MB</span><br></pre></td></tr></table></figure>

<p>此外，也可以通过指定 URL 或者某个目录来导入，例如</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker import http://example.com/exampleimage.tgz example/imagerepo</span><br></pre></td></tr></table></figure>

<p>注：用户既可以使用 docker load 来导入镜像存储文件到本地镜像库，也可以使用 docker import 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息（即仅保存容器当时的快照状态），而镜像存储文件将保存完整记录，体积也要大。此外，从容器快照文件导入时可以重新指定标签等元数据信息。 </p>
<h4 id="删除容器"><a href="#删除容器" class="headerlink" title="删除容器"></a>删除容器</h4><p>可以使用 <code>docker container rm</code> 来删除一个处于终止状态的容器。例如</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ docker container rm  trusting_newton</span><br><span class="line">trusting_newton</span><br></pre></td></tr></table></figure>

<h4 id="清理所有处于终止状态的容器"><a href="#清理所有处于终止状态的容器" class="headerlink" title="清理所有处于终止状态的容器"></a>清理所有处于终止状态的容器</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker container prune</span><br></pre></td></tr></table></figure>

<h1 id="4-访问仓库"><a href="#4-访问仓库" class="headerlink" title="4. 访问仓库"></a>4. 访问仓库</h1><h4 id="Docker-Hub"><a href="#Docker-Hub" class="headerlink" title="Docker Hub"></a>Docker Hub</h4><p>你可以在 <a href="https://cloud.docker.com/" target="_blank" rel="noopener">https://cloud.docker.com</a> 免费注册一个 Docker 账号。</p>
<p>可以通过执行 <code>docker login</code> 命令交互式的输入用户名及密码来完成在命令行界面登录 Docker Hub。</p>
<p>你可以通过 <code>docker logout</code> 退出登录。</p>
<h4 id="拉取镜像"><a href="#拉取镜像" class="headerlink" title="拉取镜像"></a>拉取镜像</h4><p>你可以通过 <code>docker search</code> 命令来查找官方仓库中的镜像，并利用 <code>docker pull</code> 命令来将它下载到本地。</p>
<p>一种是类似 <code>centos</code> 这样的镜像，被称为基础镜像或根镜像。这些基础镜像由 Docker 公司创建、验证、支持、提供。这样的镜像往往使用单个单词作为名字。</p>
<p>还有一种类型，比如 <code>tianon/centos</code> 镜像，它是由 Docker 的用户创建并维护的，往往带有用户名称前缀。可以通过前缀 <code>username/</code> 来指定使用某个用户提供的镜像，比如 tianon 用户。</p>
<h4 id="推送镜像"><a href="#推送镜像" class="headerlink" title="推送镜像"></a>推送镜像</h4><p>用户也可以在登录后通过 <code>docker push</code> 命令来将自己的镜像推送到 Docker Hub。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">$ docker tag ubuntu:17.10 username/ubuntu:17.10</span><br><span class="line"></span><br><span class="line">$ docker image ls</span><br><span class="line"></span><br><span class="line">REPOSITORY                                               TAG                    IMAGE ID            CREATED             SIZE</span><br><span class="line">ubuntu                                                   17.10                  275d79972a86        6 days ago          94.6MB</span><br><span class="line">username/ubuntu                                          17.10                  275d79972a86        6 days ago          94.6MB</span><br><span class="line"></span><br><span class="line">$ docker push username/ubuntu:17.10</span><br><span class="line"></span><br><span class="line">$ docker search username</span><br><span class="line"></span><br><span class="line">NAME                      DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED</span><br><span class="line">username/ubuntu</span><br></pre></td></tr></table></figure>

<h4 id="自动创建"><a href="#自动创建" class="headerlink" title="自动创建"></a>自动创建</h4><p>有时候，用户创建了镜像，安装了某个软件，如果软件发布新版本则需要手动更新镜像。</p>
<p>而自动创建允许用户通过 Docker Hub 指定跟踪一个目标网站（目前支持 <a href="https://github.com/" target="_blank" rel="noopener">GitHub</a> 或 <a href="https://bitbucket.org/" target="_blank" rel="noopener">BitBucket</a>）上的项目，一旦项目发生新的提交或者创建新的标签（tag），Docker Hub 会自动构建镜像并推送到 Docker Hub 中。</p>
<p>要配置自动创建，包括如下的步骤：</p>
<ul>
<li>创建并登录 Docker Hub，以及目标网站；</li>
<li>在目标网站中连接帐户到 Docker Hub；</li>
<li>在 Docker Hub 中 <a href="https://registry.hub.docker.com/builds/add/" target="_blank" rel="noopener">配置一个自动创建</a>；</li>
<li>选取一个目标网站中的项目（需要含 <code>Dockerfile</code>）和分支；</li>
<li>指定 <code>Dockerfile</code> 的位置，并提交创建。</li>
</ul>
<p>之后，可以在 Docker Hub 的 <a href="https://registry.hub.docker.com/builds/" target="_blank" rel="noopener">自动创建页面</a> 中跟踪每次创建的状态。</p>
<h4 id="私有仓库"><a href="#私有仓库" class="headerlink" title="私有仓库"></a>私有仓库</h4><p><a href="https://docs.docker.com/registry/" target="_blank" rel="noopener"><code>docker-registry</code></a> 是官方提供的工具，可以用于构建私有的镜像仓库。</p>
<h1 id="5-数据管理"><a href="#5-数据管理" class="headerlink" title="5. 数据管理"></a>5. 数据管理</h1><h3 id="5-1-数据卷"><a href="#5-1-数据卷" class="headerlink" title="5.1 数据卷"></a>5.1 数据卷</h3><p> <code>数据卷</code> 是一个可供一个或多个容器使用的特殊目录，它绕过 UFS，可以提供很多有用的特性：</p>
<ul>
<li><code>数据卷</code> 可以在容器之间共享和重用</li>
<li>对 <code>数据卷</code> 的修改会立马生效</li>
<li>对 <code>数据卷</code> 的更新，不会影响镜像</li>
<li><code>数据卷</code> 默认会一直存在，即使容器被删除</li>
</ul>
<p>注意：<code>数据卷</code> 的使用，类似于 Linux 下对目录或文件进行 mount，镜像中的被指定为挂载点的目录中的文件会隐藏掉，能显示看的是挂载的 <code>数据卷</code>。</p>
<h4 id="选择-v-还是-–mount-参数"><a href="#选择-v-还是-–mount-参数" class="headerlink" title="选择 -v 还是 -–mount 参数"></a>选择 -v 还是 -–mount 参数</h4><p>Docker 新用户应该选择 <code>--mount</code> 参数，经验丰富的 Docker 使用者对 <code>-v</code> 或者 <code>--volume</code> 已经很熟悉了，但是推荐使用 <code>--mount</code> 参数。</p>
<h4 id="创建一个数据卷"><a href="#创建一个数据卷" class="headerlink" title="创建一个数据卷"></a>创建一个数据卷</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker volume create my-vol</span><br></pre></td></tr></table></figure>

<p>查看所有的 <code>数据卷</code></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ docker volume ls</span><br><span class="line"></span><br><span class="line">local               my-vol</span><br></pre></td></tr></table></figure>

<p>在主机里使用以下命令可以查看指定 <code>数据卷</code> 的信息</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">$ docker volume inspect my-vol</span><br><span class="line">[</span><br><span class="line">    &#123;</span><br><span class="line">        &quot;Driver&quot;: &quot;local&quot;,</span><br><span class="line">        &quot;Labels&quot;: &#123;&#125;,</span><br><span class="line">        &quot;Mountpoint&quot;: &quot;/var/lib/docker/volumes/my-vol/_data&quot;,</span><br><span class="line">        &quot;Name&quot;: &quot;my-vol&quot;,</span><br><span class="line">        &quot;Options&quot;: &#123;&#125;,</span><br><span class="line">        &quot;Scope&quot;: &quot;local&quot;</span><br><span class="line">    &#125;</span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<h4 id="启动一个挂载数据卷的容器"><a href="#启动一个挂载数据卷的容器" class="headerlink" title="启动一个挂载数据卷的容器"></a>启动一个挂载数据卷的容器</h4><p> 在用 <code>docker run</code> 命令的时候，使用 <code>--mount</code> 标记来将 <code>数据卷</code> 挂载到容器里。在一次 <code>docker run</code>中可以挂载多个 <code>数据卷</code>。</p>
<p>下面创建一个名为 <code>web</code> 的容器，并加载一个 <code>数据卷</code> 到容器的 <code>/webapp</code> 目录。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -d -P \</span><br><span class="line">    --name web \</span><br><span class="line">    --mount source=my-vol,target=/webapp \ 				(相似) # -v my-vol:/wepapp \</span><br><span class="line">    training/webapp \</span><br><span class="line">    python app.py</span><br></pre></td></tr></table></figure>

<h4 id="查看数据卷的具体信息"><a href="#查看数据卷的具体信息" class="headerlink" title="查看数据卷的具体信息"></a>查看数据卷的具体信息</h4><p>在主机里使用以下命令可以查看 <code>web</code> 容器的信息</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker inspect web</span><br></pre></td></tr></table></figure>

<p><code>数据卷</code> 信息在 “Mounts” Key 下面</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">&quot;Mounts&quot;: [</span><br><span class="line">    &#123;</span><br><span class="line">        &quot;Type&quot;: &quot;volume&quot;,</span><br><span class="line">        &quot;Name&quot;: &quot;my-vol&quot;,</span><br><span class="line">        &quot;Source&quot;: &quot;/var/lib/docker/volumes/my-vol/_data&quot;,</span><br><span class="line">        &quot;Destination&quot;: &quot;/app&quot;,</span><br><span class="line">        &quot;Driver&quot;: &quot;local&quot;,</span><br><span class="line">        &quot;Mode&quot;: &quot;&quot;,</span><br><span class="line">        &quot;RW&quot;: true,</span><br><span class="line">        &quot;Propagation&quot;: &quot;&quot;</span><br><span class="line">    &#125;</span><br><span class="line">],</span><br></pre></td></tr></table></figure>

<h4 id="删除数据卷"><a href="#删除数据卷" class="headerlink" title="删除数据卷"></a>删除数据卷</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker volume rm my-vol</span><br></pre></td></tr></table></figure>

<p><code>数据卷</code> 是被设计用来持久化数据的，它的生命周期独立于容器，Docker 不会在容器被删除后自动删除 <code>数据卷</code>，并且也不存在垃圾回收这样的机制来处理没有任何容器引用的 <code>数据卷</code>。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 <code>docker rm -v</code> 这个命令。</p>
<p> 无主的数据卷可能会占据很多空间，要清理请使用以下命令</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker volume prune</span><br></pre></td></tr></table></figure>

<h3 id="5-2-挂载主机目录"><a href="#5-2-挂载主机目录" class="headerlink" title="5.2 挂载主机目录"></a>5.2 挂载主机目录</h3><h4 id="挂载一个主机目录作为数据卷"><a href="#挂载一个主机目录作为数据卷" class="headerlink" title="挂载一个主机目录作为数据卷"></a>挂载一个主机目录作为数据卷</h4><p>使用 <code>--mount</code> 标记可以指定挂载一个本地主机的目录到容器中去。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -d -P \</span><br><span class="line">    --name web \</span><br><span class="line">    # -v /src/webapp:/opt/webapp \</span><br><span class="line">    --mount type=bind,source=/src/webapp,target=/opt/webapp \</span><br><span class="line">    training/webapp \</span><br><span class="line">    python app.py</span><br></pre></td></tr></table></figure>

<p>上面的命令加载主机的 <code>/src/webapp</code> 目录到容器的 <code>/opt/webapp</code>目录。这个功能在进行测试的时候十分方便，比如用户可以放置一些程序到本地目录中，来查看容器是否正常工作。本地目录的路径必须是绝对路径，以前使用 <code>-v</code> 参数时如果本地目录不存在 Docker 会自动为你创建一个文件夹，现在使用 <code>--mount</code>参数时如果本地目录不存在，Docker 会报错</p>
<p>Docker 挂载主机目录的默认权限是 <code>读写</code>，用户也可以通过增加 <code>readonly</code> 指定为 <code>只读</code>。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -d -P \</span><br><span class="line">    --name web \</span><br><span class="line">    # -v /src/webapp:/opt/webapp:ro \</span><br><span class="line">    --mount type=bind,source=/src/webapp,target=/opt/webapp,readonly \</span><br><span class="line">    training/webapp \</span><br><span class="line">    python app.py</span><br></pre></td></tr></table></figure>

<p>加了 <code>readonly</code> 之后，就挂载为 <code>只读</code> 了。如果你在容器内 <code>/opt/webapp</code> 目录新建文件，会显示如下错误</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">/opt/webapp # touch new.txt</span><br><span class="line">touch: new.txt: Read-only file system</span><br></pre></td></tr></table></figure>

<h4 id="查看数据卷的具体信息-1"><a href="#查看数据卷的具体信息-1" class="headerlink" title="查看数据卷的具体信息"></a>查看数据卷的具体信息</h4><p>在主机里使用以下命令可以查看 <code>web</code> 容器的信息</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker inspect web</span><br></pre></td></tr></table></figure>

<p><code>挂载主机目录</code> 的配置信息在 “Mounts” Key 下面</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">&quot;Mounts&quot;: [</span><br><span class="line">    &#123;</span><br><span class="line">        &quot;Type&quot;: &quot;bind&quot;,			#此处为 bind</span><br><span class="line">        &quot;Source&quot;: &quot;/src/webapp&quot;,</span><br><span class="line">        &quot;Destination&quot;: &quot;/opt/webapp&quot;,</span><br><span class="line">        &quot;Mode&quot;: &quot;&quot;,</span><br><span class="line">        &quot;RW&quot;: true,</span><br><span class="line">        &quot;Propagation&quot;: &quot;rprivate&quot;</span><br><span class="line">    &#125;</span><br><span class="line">],</span><br></pre></td></tr></table></figure>

<h4 id="挂载一个本地主机文件作为数据卷"><a href="#挂载一个本地主机文件作为数据卷" class="headerlink" title="挂载一个本地主机文件作为数据卷"></a>挂载一个本地主机文件作为数据卷</h4><p><code>--mount</code> 标记也可以从主机挂载单个文件到容器中</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ docker run --rm -it \</span><br><span class="line">   # -v $HOME/.bash_history:/root/.bash_history \</span><br><span class="line">   --mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \</span><br><span class="line">   ubuntu:17.10 \</span><br><span class="line">   bash</span><br><span class="line"></span><br><span class="line">root@2affd44b4667:/# history</span><br><span class="line">1  ls</span><br><span class="line">2  diskutil list</span><br></pre></td></tr></table></figure>

<p>这样就可以记录在容器输入过的命令了。</p>
<h1 id="6-使用网络"><a href="#6-使用网络" class="headerlink" title="6. 使用网络"></a>6. 使用网络</h1><h3 id="6-1-外部访问容器"><a href="#6-1-外部访问容器" class="headerlink" title="6.1 外部访问容器"></a>6.1 外部访问容器</h3><p>容器中可以运行一些网络应用，要让外部也可以访问这些应用，可以通过 <code>-P</code> 或 <code>-p</code> 参数来指定端口映射。</p>
<p>当使用 <code>-P</code> 标记时，Docker 会随机映射一个 <code>49000~49900</code> 的端口到内部容器开放的网络端口。</p>
<p>使用 <code>docker container ls</code> 可以看到，本地主机的 49155 被映射到了容器的 5000 端口。此时访问本机的 49155 端口即可访问容器内 web 应用提供的界面。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -d -P training/webapp python app.py</span><br><span class="line"></span><br><span class="line">$ docker container ls -l</span><br><span class="line">CONTAINER ID  IMAGE                   COMMAND       CREATED        STATUS        PORTS                    NAMES</span><br><span class="line">bc533791f3f5  training/webapp:latest  python app.py 5 seconds ago  Up 2 seconds  0.0.0.0:49155-&gt;5000/tcp  nostalgic_morse</span><br></pre></td></tr></table></figure>



<p><code>-p</code> 则可以指定要映射的端口，并且，在一个指定端口上只可以绑定一个容器。支持的格式有 </p>
<p><code>ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort</code>。</p>
<h4 id="映射所有接口地址"><a href="#映射所有接口地址" class="headerlink" title="映射所有接口地址"></a>映射所有接口地址</h4><p>使用 <code>hostPort:containerPort</code> 格式本地的 5000 端口映射到容器的 5000 端口，可以执行</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -d -p 5000:5000 training/webapp python app.py</span><br></pre></td></tr></table></figure>

<p>此时默认会绑定本地所有接口上的所有地址。</p>
<h4 id="映射到指定地址的指定端口"><a href="#映射到指定地址的指定端口" class="headerlink" title="映射到指定地址的指定端口"></a>映射到指定地址的指定端口</h4><p>可以使用 <code>ip:hostPort:containerPort</code> 格式指定映射使用一个特定地址，比如 localhost 地址 127.0.0.1</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py</span><br></pre></td></tr></table></figure>

<h4 id="映射到指定地址的任意端口"><a href="#映射到指定地址的任意端口" class="headerlink" title="映射到指定地址的任意端口"></a>映射到指定地址的任意端口</h4><p>使用 <code>ip::containerPort</code> 绑定 localhost 的任意端口到容器的 5000 端口，本地主机会自动分配一个端口。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -d -p 127.0.0.1::5000 training/webapp python app.py</span><br></pre></td></tr></table></figure>

<p>还可以使用 <code>udp</code> 标记来指定 <code>udp</code> 端口</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py</span><br></pre></td></tr></table></figure>

<h4 id="查看映射端口配置"><a href="#查看映射端口配置" class="headerlink" title="查看映射端口配置"></a>查看映射端口配置</h4><p> 使用 <code>docker port</code> 来查看当前映射的端口配置，也可以查看到绑定的地址</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ docker port nostalgic_morse 5000</span><br><span class="line">127.0.0.1:49155.</span><br></pre></td></tr></table></figure>

<ul>
<li>容器有自己的内部网络和 ip 地址（使用 <code>docker inspect</code> 可以获取所有的变量，Docker 还可以有一个可变的网络配置。）</li>
<li><code>-p</code> 标记可以多次使用来绑定多个端口</li>
</ul>
<p>例如</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -d \</span><br><span class="line">    -p 5000:5000 \</span><br><span class="line">    -p 3000:80 \</span><br><span class="line">    training/webapp \</span><br><span class="line">    python app.py</span><br></pre></td></tr></table></figure>

<h3 id="6-2-容器互联"><a href="#6-2-容器互联" class="headerlink" title="6.2 容器互联"></a>6.2 容器互联</h3><h4 id="新建网络"><a href="#新建网络" class="headerlink" title="新建网络"></a>新建网络</h4><p> 下面先创建一个新的 Docker 网络。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker network create -d bridge my-net</span><br></pre></td></tr></table></figure>

<p><code>-d</code> 参数指定 Docker 网络类型，有 <code>bridge</code> <code>overlay</code>。其中 <code>overlay</code> 网络类型用于 <a href="https://yeasy.gitbooks.io/docker_practice/content/swarm_mode" target="_blank" rel="noopener">Swarm mode</a></p>
<h4 id="连接容器"><a href="#连接容器" class="headerlink" title="连接容器"></a>连接容器</h4><p>运行一个容器并连接到新建的 <code>my-net</code> 网络</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -it --rm --name busybox1 --network my-net busybox sh</span><br></pre></td></tr></table></figure>

<p>打开新的终端，再运行一个容器并加入到 <code>my-net</code> 网络</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -it --rm --name busybox2 --network my-net busybox sh</span><br></pre></td></tr></table></figure>

<p>再打开一个新的终端查看容器信息</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ docker container ls</span><br><span class="line"></span><br><span class="line">CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES</span><br><span class="line">b47060aca56b        busybox             &quot;sh&quot;                11 minutes ago      Up 11 minutes                           busybox2</span><br><span class="line">8720575823ec        busybox             &quot;sh&quot;                16 minutes ago      Up 16 minutes                           busybox1</span><br></pre></td></tr></table></figure>

<p>下面通过 <code>ping</code> 来证明 <code>busybox1</code> 容器和 <code>busybox2</code> 容器建立了互联关系。</p>
<p>在 <code>busybox1</code> 容器输入以下命令</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">/ # ping busybox2</span><br><span class="line">PING busybox2 (172.19.0.3): 56 data bytes</span><br><span class="line">64 bytes from 172.19.0.3: seq=0 ttl=64 time=0.072 ms</span><br><span class="line">64 bytes from 172.19.0.3: seq=1 ttl=64 time=0.118 ms</span><br></pre></td></tr></table></figure>

<p>用 ping 来测试连接 <code>busybox2</code> 容器，它会解析成 <code>172.19.0.3</code>。</p>
<p>同理在 <code>busybox2</code> 容器执行 <code>ping busybox1</code>，也会成功连接到。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">/ # ping busybox1</span><br><span class="line">PING busybox1 (172.19.0.2): 56 data bytes</span><br><span class="line">64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.064 ms</span><br><span class="line">64 bytes from 172.19.0.2: seq=1 ttl=64 time=0.143 ms</span><br></pre></td></tr></table></figure>

<p>这样，<code>busybox1</code> 容器和 <code>busybox2</code> 容器建立了互联关系。</p>
<p>如果你有多个容器之间需要互相连接，推荐使用 <a href="https://yeasy.gitbooks.io/docker_practice/content/compose" target="_blank" rel="noopener">Docker Compose</a>。</p>
<h3 id="6-3-配置-DNS"><a href="#6-3-配置-DNS" class="headerlink" title="6.3 配置 DNS"></a>6.3 配置 DNS</h3><p> 如何自定义配置容器的主机名和 DNS 呢？秘诀就是 Docker 利用虚拟文件来挂载容器的 3 个相关配置文件。</p>
<p>在容器中使用 <code>mount</code> 命令可以看到挂载信息：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ mount</span><br><span class="line">/dev/disk/by-uuid/1fec...ebdf on /etc/hostname type ext4 ...</span><br><span class="line">/dev/disk/by-uuid/1fec...ebdf on /etc/hosts type ext4 ...</span><br><span class="line">tmpfs on /etc/resolv.conf type tmpfs ...</span><br></pre></td></tr></table></figure>

<p>这种机制可以让宿主主机 DNS 信息发生更新后，所有 Docker 容器的 DNS 配置通过 <code>/etc/resolv.conf</code>文件立刻得到更新。</p>
<p>配置全部容器的 DNS ，也可以在 <code>/etc/docker/daemon.json</code> 文件中增加以下内容来设置。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;dns&quot; : [</span><br><span class="line">    &quot;114.114.114.114&quot;,</span><br><span class="line">    &quot;8.8.8.8&quot;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如果用户想要手动指定容器的配置，可以在使用 <code>docker run</code> 命令启动容器时加入如下参数：</p>
<p> <code>-h HOSTNAME</code> 或者 <code>--hostname=HOSTNAME</code> 设定容器的主机名，它会被写到容器内的 <code>/etc/hostname</code>和 <code>/etc/hosts</code>。但它在容器外部看不到，既不会在 <code>docker container ls</code> 中显示，也不会在其他的容器的 <code>/etc/hosts</code> 看到。</p>
<p><code>--dns=IP_ADDRESS</code> 添加 DNS 服务器到容器的 <code>/etc/resolv.conf</code> 中，让容器用这个服务器来解析所有不在 <code>/etc/hosts</code> 中的主机名。</p>
<p><code>--dns-search=DOMAIN</code> 设定容器的搜索域，当设定搜索域为 <code>.example.com</code> 时，在搜索一个名为 host 的主机时，DNS 不仅搜索 host，还会搜索 <code>host.example.com</code>。</p>
<blockquote>
<p>注意：如果在容器启动时没有指定最后两个参数，Docker 会默认用主机上的 <code>/etc/resolv.conf</code> 来配置容器。</p>
</blockquote>
<h1 id="7-高级网络配置-TODO"><a href="#7-高级网络配置-TODO" class="headerlink" title="7. 高级网络配置 TODO"></a>7. 高级网络配置 TODO</h1><h1 id="8-Docker-Compose-组成-项目"><a href="#8-Docker-Compose-组成-项目" class="headerlink" title="8. Docker Compose(组成) 项目"></a>8. Docker Compose(组成) 项目</h1><h3 id="8-A-Compose-简介"><a href="#8-A-Compose-简介" class="headerlink" title="8.A Compose 简介"></a>8.A Compose 简介</h3><p><code>Compose</code> 项目是 Docker 官方的开源项目，负责实现对 Docker 容器集群的快速编排。</p>
<p>在日常工作中，经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目，除了 Web 服务容器本身，往往还需要再加上后端的数据库服务容器，甚至还包括负载均衡容器等。</p>
<p><code>Compose</code> 恰好满足了这样的需求。它允许用户通过一个单独的 <code>docker-compose.yml</code> 模板文件（YAML 格式）来    定义一组相关联的应用容器为一个项目（project）。</p>
<p><code>Compose</code> 中有两个重要的概念：</p>
<ul>
<li>服务 (<code>service</code>)：一个应用的容器，<em>实际上可以包括若干运行相同镜像的容器实例。</em></li>
<li>项目 (<code>project</code>)：由一组关联的应用容器组成的一个完整业务单元，在 <code>docker-compose.yml</code> 文件中定义。</li>
</ul>
<p>一个项目可以由多个服务（容器）关联而成，<code>Compose</code> 面向项目进行管理。</p>
<h3 id="8-B-安装与卸载"><a href="#8-B-安装与卸载" class="headerlink" title="8.B 安装与卸载"></a>8.B 安装与卸载</h3><ul>
<li><p><code>Compose</code> 可以通过 Python 的包管理工具 <code>pip</code> 进行安装</p>
</li>
<li><p>也可以直接下载编译好的二进制文件使用</p>
</li>
<li><p>甚至能够直接在 Docker 容器中运行</p>
</li>
</ul>
<p>前两种方式是传统方式，适合本地环境下安装使用；最后一种方式则不破坏系统环境，更适合云计算场景。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ docker-compose --version</span><br><span class="line">docker-compose version 1.21.0, build 5920eb0</span><br></pre></td></tr></table></figure>

<h3 id="8-C-Compose-命令说明使用"><a href="#8-C-Compose-命令说明使用" class="headerlink" title="8.C Compose 命令说明使用"></a>8.C Compose 命令说明使用</h3><p>对于 Compose 来说，大部分命令的对象既可以是项目本身，也可以指定为项目中的服务或者容器。如果没有特别的说明，命令对象将是项目，这意味着项目中所有的服务都会受到命令影响。</p>
<p><code>docker-compose</code> 命令的基本的使用格式是</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker-compose [-f=&lt;arg&gt;...] [options] [COMMAND] [ARGS...]</span><br></pre></td></tr></table></figure>



<h4 id="命令选项"><a href="#命令选项" class="headerlink" title="命令选项"></a>命令选项</h4><ul>
<li><code>-f, --file FILE</code> 指定使用的 Compose 模板文件，默认为 <code>docker-compose.yml</code>，可以多次指定。</li>
<li><code>-p, --project-name NAME</code> 指定项目名称，默认将使用所在目录名称作为项目名。</li>
<li><code>--x-networking</code> 使用 Docker 的可拔插网络后端特性</li>
<li><code>--x-network-driver DRIVER</code> 指定网络后端的驱动，默认为 <code>bridge</code></li>
<li><code>--verbose</code> 输出更多调试信息。</li>
<li><code>-v, --version</code> 打印版本并退出。</li>
</ul>
<h4 id="命令使用说明"><a href="#命令使用说明" class="headerlink" title="命令使用说明"></a>命令使用说明</h4><h5 id="8-1-build-构建（重新构建）项目中的服务容器"><a href="#8-1-build-构建（重新构建）项目中的服务容器" class="headerlink" title="8.1 build 构建（重新构建）项目中的服务容器"></a>8.1 <code>build</code> 构建（重新构建）项目中的服务容器</h5><p>格式为 <code>docker-compose build [options] [SERVICE...]</code>。</p>
<p>服务容器一旦构建后，将会带上一个标记名，例如对于 web 项目中的一个 db 容器，可能是 web_db。</p>
<p>可以随时在项目目录下运行 <code>docker-compose build</code> 来重新构建服务。</p>
<p>选项包括：</p>
<ul>
<li><code>--force-rm</code> 删除构建过程中的临时容器。</li>
<li><code>--no-cache</code> 构建镜像过程中不使用 cache（这将加长构建过程）。</li>
<li><code>--pull</code> 始终尝试通过 pull 来获取更新版本的镜像。</li>
</ul>
<h5 id="8-2-config-验证-Compose-文件格式是否正确，若正确则显示配置，若格式错误显示错误原因"><a href="#8-2-config-验证-Compose-文件格式是否正确，若正确则显示配置，若格式错误显示错误原因" class="headerlink" title="8.2 config 验证 Compose 文件格式是否正确，若正确则显示配置，若格式错误显示错误原因"></a>8.2 <code>config</code> 验证 Compose 文件格式是否正确，若正确则显示配置，若格式错误显示错误原因</h5><h5 id="8-3-down-此命令将会停止-up-命令所启动的容器，并移除网络"><a href="#8-3-down-此命令将会停止-up-命令所启动的容器，并移除网络" class="headerlink" title="8.3 down 此命令将会停止 up 命令所启动的容器，并移除网络"></a>8.3 <code>down</code> 此命令将会停止 <code>up</code> 命令所启动的容器，并移除网络</h5><h5 id="8-4-exec-进入指定的容器"><a href="#8-4-exec-进入指定的容器" class="headerlink" title="8.4 exec 进入指定的容器"></a>8.4 <code>exec</code> 进入指定的容器</h5><h5 id="8-5-help-获得一个命令的帮助"><a href="#8-5-help-获得一个命令的帮助" class="headerlink" title="8.5 help 获得一个命令的帮助"></a>8.5 <code>help</code> 获得一个命令的帮助</h5><h5 id="8-6-images-列出-Compose-文件中包含的镜像"><a href="#8-6-images-列出-Compose-文件中包含的镜像" class="headerlink" title="8.6 images 列出 Compose 文件中包含的镜像"></a>8.6 <code>images</code> 列出 Compose 文件中包含的镜像</h5><h5 id="8-7-kill-通过发送-SIGKILL-信号来强制停止服务容器"><a href="#8-7-kill-通过发送-SIGKILL-信号来强制停止服务容器" class="headerlink" title="8.7 kill 通过发送 SIGKILL 信号来强制停止服务容器"></a>8.7 <code>kill</code> 通过发送 <code>SIGKILL</code> 信号来强制停止服务容器</h5><p>格式为 <code>docker-compose kill [options] [SERVICE...]</code>。</p>
<p>支持通过 <code>-s</code> 参数来指定发送的信号，例如通过如下指令发送 <code>SIGINT</code> 信号。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker-compose kill -s SIGINT</span><br></pre></td></tr></table></figure>

<h5 id="8-8-logs-查看服务容器的输出"><a href="#8-8-logs-查看服务容器的输出" class="headerlink" title="8.8 logs 查看服务容器的输出"></a>8.8 <code>logs</code> 查看服务容器的输出</h5><p>格式为 <code>docker-compose logs [options] [SERVICE...]</code>。</p>
<p>默认情况下，docker-compose 将对不同的服务输出使用不同的颜色来区分。可以通过 <code>--no-color</code> 来关闭颜色。</p>
<p>该命令在调试问题的时候十分有用。</p>
<h5 id="8-9-pause-暂停一个服务容器"><a href="#8-9-pause-暂停一个服务容器" class="headerlink" title="8.9 pause 暂停一个服务容器"></a>8.9 <code>pause</code> 暂停一个服务容器</h5><p>格式为 <code>docker-compose pause [SERVICE...]</code>。</p>
<h5 id="8-10-port-打印某个容器端口所映射的公共端口"><a href="#8-10-port-打印某个容器端口所映射的公共端口" class="headerlink" title="8.10 port 打印某个容器端口所映射的公共端口"></a>8.10 <code>port</code> 打印某个容器端口所映射的公共端口</h5><p>格式为 <code>docker-compose port [options] SERVICE PRIVATE_PORT</code>。</p>
<p>选项：</p>
<ul>
<li><code>--protocol=proto</code> 指定端口协议，tcp（默认值）或者 udp。</li>
<li><code>--index=index</code> 如果同一服务存在多个容器，指定命令对象容器的序号（默认为 1）。</li>
</ul>
<h5 id="8-11-ps-列出项目中目前的所有容器"><a href="#8-11-ps-列出项目中目前的所有容器" class="headerlink" title="8.11 ps 列出项目中目前的所有容器"></a>8.11 <code>ps</code> 列出项目中目前的所有容器</h5><p>格式为 <code>docker-compose ps [options] [SERVICE...]</code>。</p>
<p>选项：</p>
<ul>
<li><code>-q</code> 只打印容器的 ID 信息。</li>
</ul>
<h5 id="8-12-pull-拉取服务依赖的镜像"><a href="#8-12-pull-拉取服务依赖的镜像" class="headerlink" title="8.12 pull 拉取服务依赖的镜像"></a>8.12 <code>pull</code> 拉取服务依赖的镜像</h5><p>格式为 <code>docker-compose pull [options] [SERVICE...]</code>。</p>
<p>选项：</p>
<ul>
<li><code>--ignore-pull-failures</code> 忽略拉取镜像过程中的错误。</li>
</ul>
<h5 id="8-13-push-推送服务依赖的镜像到-Docker-镜像仓库"><a href="#8-13-push-推送服务依赖的镜像到-Docker-镜像仓库" class="headerlink" title="8.13 push 推送服务依赖的镜像到 Docker 镜像仓库"></a>8.13 <code>push</code> 推送服务依赖的镜像到 Docker 镜像仓库</h5><h5 id="8-14-restart-重启项目中的服务"><a href="#8-14-restart-重启项目中的服务" class="headerlink" title="8.14 restart 重启项目中的服务"></a>8.14 <code>restart</code> 重启项目中的服务</h5><p>格式为 <code>docker-compose restart [options] [SERVICE...]</code>。</p>
<p>选项：</p>
<ul>
<li><code>-t, --timeout TIMEOUT</code> 指定重启前停止容器的超时（默认为 10 秒）。</li>
</ul>
<h5 id="8-15-rm-删除所有（停止状态的）服务容器"><a href="#8-15-rm-删除所有（停止状态的）服务容器" class="headerlink" title="8.15 rm 删除所有（停止状态的）服务容器"></a>8.15 <code>rm</code> 删除所有（停止状态的）服务容器</h5><p>推荐先执行 <code>docker-compose stop</code> 命令来停止容器。</p>
<p>格式为 <code>docker-compose rm [options] [SERVICE...]</code>。</p>
<p>选项：</p>
<ul>
<li><code>-f, --force</code> 强制直接删除，包括非停止状态的容器。一般尽量不要使用该选项。</li>
<li><code>-v</code> 删除容器所挂载的数据卷。</li>
</ul>
<h5 id="8-16-run-在指定服务上执行一个命令"><a href="#8-16-run-在指定服务上执行一个命令" class="headerlink" title="8.16 run 在指定服务上执行一个命令"></a>8.16 <code>run</code> 在指定服务上执行一个命令</h5><p>格式为 <code>docker-compose run [options] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]</code>。</p>
<p>例如：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker-compose run ubuntu ping docker.com</span><br></pre></td></tr></table></figure>

<p>将会启动一个 ubuntu 服务容器，并执行 <code>ping docker.com</code> 命令。</p>
<p>默认情况下，如果存在关联，则所有关联的服务将会自动被启动，除非这些服务已经在运行中。</p>
<p>该命令类似启动容器后运行指定的命令，相关卷、链接等等都将会按照配置自动创建。</p>
<p>两个不同点：</p>
<ul>
<li>给定命令将会覆盖原有的自动运行命令；</li>
<li>不会自动创建端口，以避免冲突。</li>
</ul>
<p>如果不希望自动启动关联的容器，可以使用 <code>--no-deps</code> 选项，例如</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker-compose run --no-deps web python manage.py shell</span><br></pre></td></tr></table></figure>

<p>将不会启动 web 容器所关联的其它容器。</p>
<p>选项：</p>
<ul>
<li><code>-d</code> 后台运行容器。</li>
<li><code>--name NAME</code> 为容器指定一个名字。</li>
<li><code>--entrypoint CMD</code> 覆盖默认的容器启动指令。</li>
<li><code>-e KEY=VAL</code> 设置环境变量值，可多次使用选项来设置多个环境变量。</li>
<li><code>-u, --user=&quot;&quot;</code> 指定运行容器的用户名或者 uid。</li>
<li><code>--no-deps</code> 不自动启动关联的服务容器。</li>
<li><code>--rm</code> 运行命令后自动删除容器，<code>d</code> 模式下将忽略。</li>
<li><code>-p, --publish=[]</code> 映射容器端口到本地主机。</li>
<li><code>--service-ports</code> 配置服务端口并映射到本地主机。</li>
<li><code>-T</code> 不分配伪 tty，意味着依赖 tty 的指令将无法运行。</li>
</ul>
<h5 id="8-17-scale-设置指定服务运行的容器个数"><a href="#8-17-scale-设置指定服务运行的容器个数" class="headerlink" title="8.17 scale 设置指定服务运行的容器个数"></a>8.17 <code>scale</code> 设置指定服务运行的容器个数</h5><p>格式为 <code>docker-compose scale [options] [SERVICE=NUM...]</code>。</p>
<p>通过 <code>service=num</code> 的参数来设置数量。例如：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker-compose scale web=3 db=2</span><br></pre></td></tr></table></figure>

<p>将启动 3 个容器运行 web 服务，2 个容器运行 db 服务。</p>
<p>一般的，当指定数目多于该服务当前实际运行容器，将新创建并启动容器；反之，将停止容器。</p>
<p>选项：</p>
<ul>
<li><code>-t, --timeout TIMEOUT</code> 停止容器时候的超时（默认为 10 秒）。</li>
</ul>
<h5 id="8-18-start-启动已经存在的服务容器"><a href="#8-18-start-启动已经存在的服务容器" class="headerlink" title="8.18 start 启动已经存在的服务容器"></a>8.18 <code>start</code> 启动已经存在的服务容器</h5><p>格式为 <code>docker-compose start [SERVICE...]</code>。</p>
<h5 id="8-19-stop-停止已经处于运行状态的容器，但不删除它"><a href="#8-19-stop-停止已经处于运行状态的容器，但不删除它" class="headerlink" title="8.19 stop 停止已经处于运行状态的容器，但不删除它"></a>8.19 <code>stop</code> 停止已经处于运行状态的容器，但不删除它</h5><p>通过 <code>docker-compose start</code> 可以再次启动这些容器。</p>
<p>格式为 <code>docker-compose stop [options] [SERVICE...]</code>。</p>
<p>选项：</p>
<ul>
<li><code>-t, --timeout TIMEOUT</code> 停止容器时候的超时（默认为 10 秒）。</li>
</ul>
<h5 id="8-20-top-查看各个服务容器内运行的进程"><a href="#8-20-top-查看各个服务容器内运行的进程" class="headerlink" title="8.20 top 查看各个服务容器内运行的进程"></a>8.20 <code>top</code> 查看各个服务容器内运行的进程</h5><h5 id="8-21-unpause-恢复处于暂停状态中的服务"><a href="#8-21-unpause-恢复处于暂停状态中的服务" class="headerlink" title="8.21 unpause 恢复处于暂停状态中的服务"></a>8.21 <code>unpause</code> 恢复处于暂停状态中的服务</h5><p>格式为 <code>docker-compose unpause [SERVICE...]</code>。</p>
<h5 id="8-22-up-启动一个项目"><a href="#8-22-up-启动一个项目" class="headerlink" title="8.22 up 启动一个项目"></a>8.22 <code>up</code> 启动一个项目</h5><p>格式为 <code>docker-compose up [options] [SERVICE...]</code>。</p>
<p>该命令十分强大，它将尝试自动完成包括构建镜像，（重新）创建服务，启动服务，并关联服务相关容器的一系列操作。</p>
<p>链接的服务都将会被自动启动，除非已经处于运行状态。</p>
<p>可以说，大部分时候都可以直接通过该命令来启动一个项目。</p>
<p>默认情况，<code>docker-compose up</code> 启动的容器都在前台，控制台将会同时打印所有容器的输出信息，可以很方便进行调试。</p>
<p>当通过 <code>Ctrl-C</code> 停止命令时，所有容器将会停止。</p>
<p>如果使用 <code>docker-compose up -d</code>，将会在后台启动并运行所有的容器。一般推荐生产环境下使用该选项。</p>
<p>默认情况，如果服务容器已经存在，<code>docker-compose up</code> 将会尝试停止容器，然后重新创建（保持使用 <code>volumes-from</code> 挂载的卷），以保证新启动的服务匹配 <code>docker-compose.yml</code> 文件的最新内容。如果用户不希望容器被停止并重新创建，可以使用 <code>docker-compose up --no-recreate</code>。这样将只会启动处于停止状态的容器，而忽略已经运行的服务。如果用户只想重新部署某个服务，可以使用 <code>docker-compose up --no-deps -d &lt;SERVICE_NAME&gt;</code> 来重新创建服务并后台停止旧服务，启动新服务，并不会影响到其所依赖的服务。</p>
<p>选项：</p>
<ul>
<li><code>-d</code> 在后台运行服务容器。</li>
<li><code>--no-color</code> 不使用颜色来区分不同的服务的控制台输出。</li>
<li><code>--no-deps</code> 不启动服务所链接的容器。</li>
<li><code>--force-recreate</code> 强制重新创建容器，不能与 <code>--no-recreate</code> 同时使用。</li>
<li><code>--no-recreate</code> 如果容器已经存在了，则不重新创建，不能与 <code>--force-recreate</code> 同时使用。</li>
<li><code>--no-build</code> 不自动构建缺失的服务镜像。</li>
<li><code>-t, --timeout TIMEOUT</code> 停止容器时候的超时（默认为 10 秒）。</li>
</ul>
<h5 id="8-23-version-打印版本信息"><a href="#8-23-version-打印版本信息" class="headerlink" title="8.23 version 打印版本信息"></a>8.23 <code>version</code> 打印版本信息</h5><p>格式为 <code>docker-compose version</code>。</p>
<h3 id="8-D-Compose-模板文件"><a href="#8-D-Compose-模板文件" class="headerlink" title="8.D Compose 模板文件"></a>8.D Compose 模板文件</h3><p>模板文件是使用 <code>Compose</code> 的核心，涉及到的指令关键字也比较多。大部分指令跟 <code>docker run</code> 相关参数的含义都是类似的。</p>
<p>默认的模板文件名称为 <code>docker-compose.yml</code>，格式为 YAML 格式。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">version: &quot;3&quot;</span><br><span class="line"></span><br><span class="line">services:</span><br><span class="line">  webapp:</span><br><span class="line">    image: examples/web</span><br><span class="line">    ports:</span><br><span class="line">      - &quot;80:80&quot;</span><br><span class="line">    volumes:</span><br><span class="line">      - &quot;/data&quot;</span><br></pre></td></tr></table></figure>

<p>注意每个服务都必须通过 <code>image</code> 指令指定镜像或 <code>build</code> 指令（需要 Dockerfile）等来自动构建生成镜像。</p>
<p>如果使用 <code>build</code> 指令，在 <code>Dockerfile</code> 中设置的选项(例如：<code>CMD</code>, <code>EXPOSE</code>, <code>VOLUME</code>, <code>ENV</code> 等) 将会自动被获取，无需在 <code>docker-compose.yml</code> 中再次设置。</p>
<h3 id="模板文件指令-TODO"><a href="#模板文件指令-TODO" class="headerlink" title="模板文件指令 TODO:"></a>模板文件指令 TODO:</h3><h5 id="build"><a href="#build" class="headerlink" title="build"></a><code>build</code></h5><p>指定 <code>Dockerfile</code> 所在文件夹的路径（可以是绝对路径，或者相对 docker-compose.yml 文件的路径）。 <code>Compose</code> 将会利用它自动构建这个镜像，然后使用这个镜像。</p>

    </div>

    
    
    
        
      
        <div id="reward-container">
  <div>坚持原创技术分享，您的支持将鼓励我继续创作, 一个耿直的笑脸~</div>
  <button id="reward-button" disable="enable" onclick="var qr = document.getElementById(&quot;qr&quot;); qr.style.display = (qr.style.display === 'none') ? 'block' : 'none';">
    打赏
  </button>
  <div id="qr" style="display: none;">
        
      
      <div style="display: inline-block">
        <img src="/images/wechatpay.jpg" alt="Levon 微信支付">
        <p>微信支付</p>
      </div>
        
      
      <div style="display: inline-block">
        <img src="/images/alipay.jpg" alt="Levon 支付宝">
        <p>支付宝</p>
      </div>

  </div>
</div>

      

      <footer class="post-footer">
          
            
          
          <div class="post-tags">
            
              <a href="/tags/docker/" rel="tag"># docker</a>
            
          </div>
        

        

          <div class="post-nav">
            <div class="post-nav-next post-nav-item">
              
                <a href="/p/e25fe1fc.html" rel="next" title="令牌桶算法_golang_rate对速度进行限制">
                  <i class="fa fa-chevron-left"></i> 令牌桶算法_golang_rate对速度进行限制
                </a>
              
            </div>

            <span class="post-nav-divider"></span>

            <div class="post-nav-prev post-nav-item">
              
                <a href="/p/6196d525.html" rel="prev" title="golang内存模型和happens_before">
                  golang内存模型和happens_before <i class="fa fa-chevron-right"></i>
                </a>
              
            </div>
          </div>
        
      </footer>
    
  </div>
  
  
  
  </article>

  </div>


          </div>
          
    
    
  <div class="comments" id="comments">
    <div id="disqus_thread">
      <noscript>Please enable JavaScript to view the comments powered by Disqus.</noscript>
    </div>
  </div>
  
  

        </div>
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">
        
        
        
        
      

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#0-基本概念"><span class="nav-number">1.</span> <span class="nav-text">0. 基本概念</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#镜像"><span class="nav-number">1.0.0.1.</span> <span class="nav-text">镜像</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#容器"><span class="nav-number">1.0.0.2.</span> <span class="nav-text">容器</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#仓库"><span class="nav-number">1.0.0.3.</span> <span class="nav-text">仓库</span></a></li></ol></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#1-使用镜像"><span class="nav-number">2.</span> <span class="nav-text">1. 使用镜像</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#获取镜像"><span class="nav-number">2.0.0.1.</span> <span class="nav-text">获取镜像</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#运行容器"><span class="nav-number">2.0.0.2.</span> <span class="nav-text">运行容器</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#列出镜像"><span class="nav-number">2.0.0.3.</span> <span class="nav-text">列出镜像</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#虚悬镜像"><span class="nav-number">2.0.0.4.</span> <span class="nav-text">虚悬镜像</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#删除本地镜像"><span class="nav-number">2.0.0.5.</span> <span class="nav-text">删除本地镜像</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#利用-commit-理解镜像构成"><span class="nav-number">2.0.0.6.</span> <span class="nav-text">利用 commit 理解镜像构成</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#慎用-docker-commit"><span class="nav-number">2.0.0.7.</span> <span class="nav-text">慎用 docker commit</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#null"><span class="nav-number">2.0.0.8.</span> <span class="nav-text"></span></a></li></ol></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#2-Dockerfile-定制镜像"><span class="nav-number">3.</span> <span class="nav-text">2. Dockerfile 定制镜像</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#2-A-构建镜像"><span class="nav-number">3.0.1.</span> <span class="nav-text">2.A 构建镜像</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#2-1-镜像构建上下文（Context）-上下文路径就是-docker-build-指定的"><span class="nav-number">3.0.1.1.</span> <span class="nav-text">2.1 镜像构建上下文（Context） 上下文路径就是 docker build 指定的</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-2-其它-docker-build-的用法"><span class="nav-number">3.0.1.2.</span> <span class="nav-text">2.2 其它 docker build 的用法</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#1-直接用-Git-repo-进行构建"><span class="nav-number">3.0.1.2.1.</span> <span class="nav-text">1. 直接用 Git repo 进行构建</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#2-用给定的-tar-压缩包构建"><span class="nav-number">3.0.1.2.2.</span> <span class="nav-text">2. 用给定的 tar 压缩包构建</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#3-从标准输入中读取-Dockerfile-进行构建"><span class="nav-number">3.0.1.2.3.</span> <span class="nav-text">3. 从标准输入中读取 Dockerfile 进行构建</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#4-从标准输入中读取上下文压缩包进行构建"><span class="nav-number">3.0.1.2.4.</span> <span class="nav-text">4. 从标准输入中读取上下文压缩包进行构建</span></a></li></ol></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-B-dockerfile-指令"><span class="nav-number">3.0.2.</span> <span class="nav-text">2.B dockerfile 指令</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#2-1-FROM-指定基础镜像"><span class="nav-number">3.0.2.1.</span> <span class="nav-text">2.1 FROM 指定基础镜像</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-2-RUN-执行命令"><span class="nav-number">3.0.2.2.</span> <span class="nav-text">2.2 RUN 执行命令</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-3-COPY-复制文件"><span class="nav-number">3.0.2.3.</span> <span class="nav-text">2.3 COPY 复制文件</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-4-ADD-更高级的复制文件"><span class="nav-number">3.0.2.4.</span> <span class="nav-text">2.4 ADD 更高级的复制文件</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-5-CMD-容器启动命令"><span class="nav-number">3.0.2.5.</span> <span class="nav-text">2.5 CMD 容器启动命令</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-6-ENTRYPOINT-入口点"><span class="nav-number">3.0.2.6.</span> <span class="nav-text">2.6 ENTRYPOINT 入口点</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#场景一：让镜像变成像命令一样使用"><span class="nav-number">3.0.2.6.1.</span> <span class="nav-text">场景一：让镜像变成像命令一样使用</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#场景二：应用运行前的准备工作"><span class="nav-number">3.0.2.6.2.</span> <span class="nav-text">场景二：应用运行前的准备工作</span></a></li></ol></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-7-ENV-设置环境变量"><span class="nav-number">3.0.2.7.</span> <span class="nav-text">2.7 ENV 设置环境变量</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-8-ARG-构建参数-构建环境的环境变量"><span class="nav-number">3.0.2.8.</span> <span class="nav-text">2.8 ARG 构建参数(构建环境的环境变量)</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-9-VOLUME-定义匿名卷"><span class="nav-number">3.0.2.9.</span> <span class="nav-text">2.9 VOLUME 定义匿名卷</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-10-EXPOSE-声明端口"><span class="nav-number">3.0.2.10.</span> <span class="nav-text">2.10 EXPOSE 声明端口</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-11-WORKDIR-指定工作目录"><span class="nav-number">3.0.2.11.</span> <span class="nav-text">2.11 WORKDIR 指定工作目录</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-12-USER-指定当前用户"><span class="nav-number">3.0.2.12.</span> <span class="nav-text">2.12 USER 指定当前用户</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-13-HEALTHCHECK-健康检查"><span class="nav-number">3.0.2.13.</span> <span class="nav-text">2.13 HEALTHCHECK 健康检查</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-14-ONBUILD-为他人做嫁衣裳"><span class="nav-number">3.0.2.14.</span> <span class="nav-text">2.14 ONBUILD 为他人做嫁衣裳</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-C-docker多阶段构建-多个-FROM-as"><span class="nav-number">3.0.3.</span> <span class="nav-text">2.C docker多阶段构建 (多个 FROM as)</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#只构建某一阶段的镜像"><span class="nav-number">3.0.3.1.</span> <span class="nav-text">只构建某一阶段的镜像</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#构建时从其他镜像复制文件"><span class="nav-number">3.0.3.2.</span> <span class="nav-text">构建时从其他镜像复制文件</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-D-其它制作镜像的方式"><span class="nav-number">3.0.4.</span> <span class="nav-text">2.D 其它制作镜像的方式</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#2-1-从-rootfs-压缩包导入"><span class="nav-number">3.0.4.1.</span> <span class="nav-text">2.1 从 rootfs 压缩包导入</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-2-docker-save-和-docker-load"><span class="nav-number">3.0.4.2.</span> <span class="nav-text">2.2 docker save 和 docker load</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#保存镜像"><span class="nav-number">3.0.4.2.1.</span> <span class="nav-text">保存镜像</span></a></li></ol></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-3-docker-export-save-区别"><span class="nav-number">3.0.4.3.</span> <span class="nav-text">2.3 docker export save 区别</span></a></li></ol></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#3-操作容器"><span class="nav-number">4.</span> <span class="nav-text">3. 操作容器</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#新建并启动"><span class="nav-number">4.0.0.1.</span> <span class="nav-text">新建并启动</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#启动已终止容器"><span class="nav-number">4.0.0.2.</span> <span class="nav-text">启动已终止容器</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#后台运行"><span class="nav-number">4.0.0.3.</span> <span class="nav-text">后台运行</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#终止容器"><span class="nav-number">4.0.0.4.</span> <span class="nav-text">终止容器</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#进入容器"><span class="nav-number">4.0.0.5.</span> <span class="nav-text">进入容器</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#导出容器"><span class="nav-number">4.0.0.6.</span> <span class="nav-text">导出容器</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#导入容器快照"><span class="nav-number">4.0.0.7.</span> <span class="nav-text">导入容器快照</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#删除容器"><span class="nav-number">4.0.0.8.</span> <span class="nav-text">删除容器</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#清理所有处于终止状态的容器"><span class="nav-number">4.0.0.9.</span> <span class="nav-text">清理所有处于终止状态的容器</span></a></li></ol></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#4-访问仓库"><span class="nav-number">5.</span> <span class="nav-text">4. 访问仓库</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#Docker-Hub"><span class="nav-number">5.0.0.1.</span> <span class="nav-text">Docker Hub</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#拉取镜像"><span class="nav-number">5.0.0.2.</span> <span class="nav-text">拉取镜像</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#推送镜像"><span class="nav-number">5.0.0.3.</span> <span class="nav-text">推送镜像</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#自动创建"><span class="nav-number">5.0.0.4.</span> <span class="nav-text">自动创建</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#私有仓库"><span class="nav-number">5.0.0.5.</span> <span class="nav-text">私有仓库</span></a></li></ol></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#5-数据管理"><span class="nav-number">6.</span> <span class="nav-text">5. 数据管理</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#5-1-数据卷"><span class="nav-number">6.0.1.</span> <span class="nav-text">5.1 数据卷</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#选择-v-还是-–mount-参数"><span class="nav-number">6.0.1.1.</span> <span class="nav-text">选择 -v 还是 -–mount 参数</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#创建一个数据卷"><span class="nav-number">6.0.1.2.</span> <span class="nav-text">创建一个数据卷</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#启动一个挂载数据卷的容器"><span class="nav-number">6.0.1.3.</span> <span class="nav-text">启动一个挂载数据卷的容器</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#查看数据卷的具体信息"><span class="nav-number">6.0.1.4.</span> <span class="nav-text">查看数据卷的具体信息</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#删除数据卷"><span class="nav-number">6.0.1.5.</span> <span class="nav-text">删除数据卷</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#5-2-挂载主机目录"><span class="nav-number">6.0.2.</span> <span class="nav-text">5.2 挂载主机目录</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#挂载一个主机目录作为数据卷"><span class="nav-number">6.0.2.1.</span> <span class="nav-text">挂载一个主机目录作为数据卷</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#查看数据卷的具体信息-1"><span class="nav-number">6.0.2.2.</span> <span class="nav-text">查看数据卷的具体信息</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#挂载一个本地主机文件作为数据卷"><span class="nav-number">6.0.2.3.</span> <span class="nav-text">挂载一个本地主机文件作为数据卷</span></a></li></ol></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#6-使用网络"><span class="nav-number">7.</span> <span class="nav-text">6. 使用网络</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#6-1-外部访问容器"><span class="nav-number">7.0.1.</span> <span class="nav-text">6.1 外部访问容器</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#映射所有接口地址"><span class="nav-number">7.0.1.1.</span> <span class="nav-text">映射所有接口地址</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#映射到指定地址的指定端口"><span class="nav-number">7.0.1.2.</span> <span class="nav-text">映射到指定地址的指定端口</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#映射到指定地址的任意端口"><span class="nav-number">7.0.1.3.</span> <span class="nav-text">映射到指定地址的任意端口</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#查看映射端口配置"><span class="nav-number">7.0.1.4.</span> <span class="nav-text">查看映射端口配置</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#6-2-容器互联"><span class="nav-number">7.0.2.</span> <span class="nav-text">6.2 容器互联</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#新建网络"><span class="nav-number">7.0.2.1.</span> <span class="nav-text">新建网络</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#连接容器"><span class="nav-number">7.0.2.2.</span> <span class="nav-text">连接容器</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#6-3-配置-DNS"><span class="nav-number">7.0.3.</span> <span class="nav-text">6.3 配置 DNS</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#7-高级网络配置-TODO"><span class="nav-number">8.</span> <span class="nav-text">7. 高级网络配置 TODO</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#8-Docker-Compose-组成-项目"><span class="nav-number">9.</span> <span class="nav-text">8. Docker Compose(组成) 项目</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#8-A-Compose-简介"><span class="nav-number">9.0.1.</span> <span class="nav-text">8.A Compose 简介</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#8-B-安装与卸载"><span class="nav-number">9.0.2.</span> <span class="nav-text">8.B 安装与卸载</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#8-C-Compose-命令说明使用"><span class="nav-number">9.0.3.</span> <span class="nav-text">8.C Compose 命令说明使用</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#命令选项"><span class="nav-number">9.0.3.1.</span> <span class="nav-text">命令选项</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#命令使用说明"><span class="nav-number">9.0.3.2.</span> <span class="nav-text">命令使用说明</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#8-1-build-构建（重新构建）项目中的服务容器"><span class="nav-number">9.0.3.2.1.</span> <span class="nav-text">8.1 build 构建（重新构建）项目中的服务容器</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-2-config-验证-Compose-文件格式是否正确，若正确则显示配置，若格式错误显示错误原因"><span class="nav-number">9.0.3.2.2.</span> <span class="nav-text">8.2 config 验证 Compose 文件格式是否正确，若正确则显示配置，若格式错误显示错误原因</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-3-down-此命令将会停止-up-命令所启动的容器，并移除网络"><span class="nav-number">9.0.3.2.3.</span> <span class="nav-text">8.3 down 此命令将会停止 up 命令所启动的容器，并移除网络</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-4-exec-进入指定的容器"><span class="nav-number">9.0.3.2.4.</span> <span class="nav-text">8.4 exec 进入指定的容器</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-5-help-获得一个命令的帮助"><span class="nav-number">9.0.3.2.5.</span> <span class="nav-text">8.5 help 获得一个命令的帮助</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-6-images-列出-Compose-文件中包含的镜像"><span class="nav-number">9.0.3.2.6.</span> <span class="nav-text">8.6 images 列出 Compose 文件中包含的镜像</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-7-kill-通过发送-SIGKILL-信号来强制停止服务容器"><span class="nav-number">9.0.3.2.7.</span> <span class="nav-text">8.7 kill 通过发送 SIGKILL 信号来强制停止服务容器</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-8-logs-查看服务容器的输出"><span class="nav-number">9.0.3.2.8.</span> <span class="nav-text">8.8 logs 查看服务容器的输出</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-9-pause-暂停一个服务容器"><span class="nav-number">9.0.3.2.9.</span> <span class="nav-text">8.9 pause 暂停一个服务容器</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-10-port-打印某个容器端口所映射的公共端口"><span class="nav-number">9.0.3.2.10.</span> <span class="nav-text">8.10 port 打印某个容器端口所映射的公共端口</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-11-ps-列出项目中目前的所有容器"><span class="nav-number">9.0.3.2.11.</span> <span class="nav-text">8.11 ps 列出项目中目前的所有容器</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-12-pull-拉取服务依赖的镜像"><span class="nav-number">9.0.3.2.12.</span> <span class="nav-text">8.12 pull 拉取服务依赖的镜像</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-13-push-推送服务依赖的镜像到-Docker-镜像仓库"><span class="nav-number">9.0.3.2.13.</span> <span class="nav-text">8.13 push 推送服务依赖的镜像到 Docker 镜像仓库</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-14-restart-重启项目中的服务"><span class="nav-number">9.0.3.2.14.</span> <span class="nav-text">8.14 restart 重启项目中的服务</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-15-rm-删除所有（停止状态的）服务容器"><span class="nav-number">9.0.3.2.15.</span> <span class="nav-text">8.15 rm 删除所有（停止状态的）服务容器</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-16-run-在指定服务上执行一个命令"><span class="nav-number">9.0.3.2.16.</span> <span class="nav-text">8.16 run 在指定服务上执行一个命令</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-17-scale-设置指定服务运行的容器个数"><span class="nav-number">9.0.3.2.17.</span> <span class="nav-text">8.17 scale 设置指定服务运行的容器个数</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-18-start-启动已经存在的服务容器"><span class="nav-number">9.0.3.2.18.</span> <span class="nav-text">8.18 start 启动已经存在的服务容器</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-19-stop-停止已经处于运行状态的容器，但不删除它"><span class="nav-number">9.0.3.2.19.</span> <span class="nav-text">8.19 stop 停止已经处于运行状态的容器，但不删除它</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-20-top-查看各个服务容器内运行的进程"><span class="nav-number">9.0.3.2.20.</span> <span class="nav-text">8.20 top 查看各个服务容器内运行的进程</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-21-unpause-恢复处于暂停状态中的服务"><span class="nav-number">9.0.3.2.21.</span> <span class="nav-text">8.21 unpause 恢复处于暂停状态中的服务</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-22-up-启动一个项目"><span class="nav-number">9.0.3.2.22.</span> <span class="nav-text">8.22 up 启动一个项目</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-23-version-打印版本信息"><span class="nav-number">9.0.3.2.23.</span> <span class="nav-text">8.23 version 打印版本信息</span></a></li></ol></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#8-D-Compose-模板文件"><span class="nav-number">9.0.4.</span> <span class="nav-text">8.D Compose 模板文件</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#模板文件指令-TODO"><span class="nav-number">9.0.5.</span> <span class="nav-text">模板文件指令 TODO:</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#build"><span class="nav-number">9.0.5.0.1.</span> <span class="nav-text">build</span></a></li></ol></li></ol></li></ol></li></ol></li></ol></div>
        
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image"
      src="/images/avatar.jpg"
      alt="Levon">
  <p class="site-author-name" itemprop="name">Levon</p>
  <div class="site-description" itemprop="description">Never Give Up</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
        
          <a href="/archives/">
        
          <span class="site-state-item-count">127</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
    
      
      
      <div class="site-state-item site-state-categories">
        
          
            <a href="/categories/">
          
        
        <span class="site-state-item-count">34</span>
        <span class="site-state-item-name">分类</span>
        </a>
      </div>
    
      
      
      <div class="site-state-item site-state-tags">
        
          
            <a href="/tags/">
          
        
        <span class="site-state-item-count">76</span>
        <span class="site-state-item-name">标签</span>
        </a>
      </div>
    
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
      
      
        
      
      
        
      
        <a href="https://github.com/unix2dos" title="GitHub &rarr; https://github.com/unix2dos" rel="noopener" target="_blank"><i class="fa fa-fw fa-github"></i>GitHub</a>
      </span>
    
      <span class="links-of-author-item">
      
      
        
      
      
        
      
        <a href="mailto:levonfly@gmail.com" title="E-Mail &rarr; mailto:levonfly@gmail.com" rel="noopener" target="_blank"><i class="fa fa-fw fa-envelope"></i>E-Mail</a>
      </span>
    
      <span class="links-of-author-item">
      
      
        
      
      
        
      
        <a href="https://twitter.com/levonfly" title="Twitter &rarr; https://twitter.com/levonfly" rel="noopener" target="_blank"><i class="fa fa-fw fa-twitter"></i>Twitter</a>
      </span>
    
      <span class="links-of-author-item">
      
      
        
      
      
        
      
        <a href="https://weibo.com/l6241425" title="Weibo &rarr; https://weibo.com/l6241425" rel="noopener" target="_blank"><i class="fa fa-fw fa-weibo"></i>Weibo</a>
      </span>
    
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2020</span>
  <span class="with-love" id="animate">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Levon</span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-area-chart"></i>
    </span>
    <span title="站点总字数">507k</span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-coffee"></i>
    </span>
    <span title="站点阅读时长">7:41</span>
</div>

        












        
      </div>
    </footer>
  </div>

  
  <script src="/lib/anime.min.js?v=3.1.0"></script>
  <script src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  <script src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
<script src="/js/utils.js?v=7.4.0"></script><script src="/js/motion.js?v=7.4.0"></script>
<script src="/js/schemes/pisces.js?v=7.4.0"></script>

<script src="/js/next-boot.js?v=7.4.0"></script>



  
  <script>
    (function(){
      var bp = document.createElement('script');
      var curProtocol = window.location.protocol.split(':')[0];
      bp.src = (curProtocol === 'https') ? 'https://zz.bdstatic.com/linksubmit/push.js' : 'http://push.zhanzhang.baidu.com/push.js';
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(bp, s);
    })();
  </script>








  <script src="/js/local-search.js?v=7.4.0"></script>














  

  

  

<script>
  function loadCount() {
    var d = document, s = d.createElement('script');
    s.src = 'https://levonfly.disqus.com/count.js';
    s.id = 'dsq-count-scr';
    (d.head || d.body).appendChild(s);
  }
  // defer loading until the whole page loading is completed
  window.addEventListener('load', loadCount, false);
</script>
<script>
  function disqus_config() {
    this.page.url = "https://unix2dos.github.io/p/3fc66628.html";
    this.page.identifier = "p/3fc66628.html";
    this.page.title = 'docker从入门到实践';};
  function loadComments() {
    if (window.DISQUS) {
      DISQUS.reset({
        reload: true,
        config: disqus_config
      });
    } else {
      var d = document, s = d.createElement('script');
      s.src = 'https://levonfly.disqus.com/embed.js';
      s.setAttribute('data-timestamp', '' + +new Date());
      (d.head || d.body).appendChild(s);
    }
  }
    window.addEventListener('load', loadComments, false);
  
</script>

</body>
</html>
